summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-28 07:36:32 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-28 07:36:32 -1000
commitc339f4010dea8b0d754f84ace70cd1b089878ef8 (patch)
tree7494ea078a05b0b824c3f263260e45c1f78bcfd3
parentd738fbd1a8b9d82ef5ea0d54a9e2b3ab7f1c24dd (diff)
parent9d3475065167197742b0882aff21cf8e78393277 (diff)
Merge tag 'platform-drivers-x86-v7.4-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Ilpo Järvinen: "The usual updates for laptop quirks from HP, ASUS, Acer, Dell etc: - new model names and recent CPU support - more special button / function key handling - battery and hardware monitoring quirks Tool updates to intel-speed-select WMI now supports non-x86 architectures Miscellaneous cleanups / refactoring / improvements" * tag 'platform-drivers-x86-v7.4-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (237 commits) platform/x86: asus-wmi: name the TUF keyboard RGB state and speed codes platform/x86: asus-wmi: fix TUF keyboard power state shutdown naming platform/x86: asus-wmi: rename TUF RGB state DEVID to _CMD platform/x86: asus-armoury: add power limits quirk for GU605CM platform/x86: asus-armoury: add power limits quirk for G533QS platform/x86: asus-armoury: add power limits quirk for GA402NV platform/x86: asus-armoury: add power limits quirk for FA608UP platform/x86: asus-armoury: add power limits quirk for FA608UH platform/x86: asus-armoury: add power limits quirk for FA608PM platform/x86: asus-armoury: add power limits quirk for G713QR platform/x86: ibm_rtl: Fix potential OOB read at signature search platform/x86: acer-wmi: Add support for Acer Nitro AN16-41 platform/x86: acer-wmi: Cycle platform profile on mode key 0x4 platform/x86: hp-wmi: Disable hwmon attributes when no fans are usable platform/x86: gpd pocket fan: Cancel work before releasing GPIOs platform/x86: asus-armoury: Add XBOX ROG Ally X 20 Tunables platform/x86: msi-wmi: Ignore WMI events for keys also handled by atkbd platform/x86: msi-wmi: Sort scancodes numerically platform: arm64: Add a driver for the EC found on ASUS Glymur machines MAINTAINERS: Add myself as a LENOVO drivers co-maintainer ...
-rw-r--r--Documentation/ABI/removed/sysfs-driver-uniwill-laptop10
-rw-r--r--Documentation/ABI/testing/debugfs-tpmi8
-rw-r--r--Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec22
-rw-r--r--Documentation/ABI/testing/sysfs-driver-uniwill-laptop15
-rw-r--r--Documentation/admin-guide/laptops/thinkpad-acpi.rst26
-rw-r--r--Documentation/admin-guide/laptops/uniwill-laptop.rst9
-rw-r--r--Documentation/arch/x86/amd_hsmp.rst24
-rw-r--r--Documentation/wmi/devices/acer-wmi-battery.rst70
-rw-r--r--Documentation/wmi/devices/bitland-mifs-wmi.rst64
-rw-r--r--MAINTAINERS9
-rw-r--r--arch/x86/include/uapi/asm/amd_hsmp.h420
-rw-r--r--drivers/acpi/Kconfig2
-rw-r--r--drivers/acpi/video_detect.c8
-rw-r--r--drivers/gpu/drm/amd/amdgpu/Kconfig4
-rw-r--r--drivers/gpu/drm/gma500/Kconfig1
-rw-r--r--drivers/gpu/drm/i915/Kconfig1
-rw-r--r--drivers/gpu/drm/radeon/Kconfig4
-rw-r--r--drivers/gpu/drm/xe/Kconfig5
-rw-r--r--drivers/platform/arm64/Kconfig40
-rw-r--r--drivers/platform/arm64/Makefile2
-rw-r--r--drivers/platform/arm64/acer-aspire1-ec.c2
-rw-r--r--drivers/platform/arm64/asus-glymur-ec.c617
-rw-r--r--drivers/platform/arm64/huawei-gaokun-ec.c2
-rw-r--r--drivers/platform/arm64/lenovo-thinkpad-t14s.c10
-rw-r--r--drivers/platform/arm64/lenovo-yoga-c630.c2
-rw-r--r--drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c530
-rw-r--r--drivers/platform/arm64/qcom-hamoa-ec.c19
-rw-r--r--drivers/platform/loongarch/Kconfig1
-rw-r--r--drivers/platform/mellanox/mlxbf-tmfifo.c1
-rw-r--r--drivers/platform/mellanox/mlxreg-hotplug.c4
-rw-r--r--drivers/platform/olpc/olpc-ec.c2
-rw-r--r--drivers/platform/surface/surface_aggregator_registry.c7
-rw-r--r--drivers/platform/wmi/Kconfig14
-rw-r--r--drivers/platform/wmi/Makefile4
-rw-r--r--drivers/platform/wmi/bmof.c (renamed from drivers/platform/x86/wmi-bmof.c)0
-rw-r--r--drivers/platform/wmi/core.c33
-rw-r--r--drivers/platform/x86/Kconfig24
-rw-r--r--drivers/platform/x86/Makefile4
-rw-r--r--drivers/platform/x86/acer-wmi-battery.c450
-rw-r--r--drivers/platform/x86/acer-wmi.c94
-rw-r--r--drivers/platform/x86/amd/hfi/hfi.c34
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c31
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c248
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h24
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c26
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c26
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.h1
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c76
-rw-r--r--drivers/platform/x86/amd/pmf/core.c23
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h150
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c23
-rw-r--r--drivers/platform/x86/amd/pmf/sps.c15
-rw-r--r--drivers/platform/x86/amd/pmf/util.c1
-rw-r--r--drivers/platform/x86/asus-armoury.c309
-rw-r--r--drivers/platform/x86/asus-armoury.h408
-rw-r--r--drivers/platform/x86/asus-laptop.c2
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c25
-rw-r--r--drivers/platform/x86/asus-tf103c-dock.c2
-rw-r--r--drivers/platform/x86/asus-wmi.c233
-rw-r--r--drivers/platform/x86/asus-wmi.h1
-rw-r--r--drivers/platform/x86/ayaneo-ec.c7
-rw-r--r--drivers/platform/x86/bitland-mifs-wmi.c146
-rw-r--r--drivers/platform/x86/dell/alienware-wmi-wmax.c8
-rw-r--r--drivers/platform/x86/dell/dell-dw5826e-reset.c30
-rw-r--r--drivers/platform/x86/dell/dell-wmi-aio.c205
-rw-r--r--drivers/platform/x86/gpd-pocket-fan.c9
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.c179
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.h3
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c57
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/int-attributes.c64
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c69
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c75
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c12
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/string-attributes.c69
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c18
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c323
-rw-r--r--drivers/platform/x86/hp/hp_accel.c2
-rw-r--r--drivers/platform/x86/huawei-wmi.c1
-rw-r--r--drivers/platform/x86/ibm_rtl.c8
-rw-r--r--drivers/platform/x86/intel/Kconfig4
-rw-r--r--drivers/platform/x86/intel/Makefile1
-rw-r--r--drivers/platform/x86/intel/bxtwc_tmu.c5
-rw-r--r--drivers/platform/x86/intel/bytcrc_pwrsrc.c2
-rw-r--r--drivers/platform/x86/intel/crystal_cove_charger.c2
-rw-r--r--drivers/platform/x86/intel/hid.c7
-rw-r--r--drivers/platform/x86/intel/int0002_vgpio.c4
-rw-r--r--drivers/platform/x86/intel/pmc/core.c1
-rw-r--r--drivers/platform/x86/intel/pmc/core.h2
-rw-r--r--drivers/platform/x86/intel/pmc/nvl.c2
-rw-r--r--drivers/platform/x86/intel/pmt/class.c35
-rw-r--r--drivers/platform/x86/intel/pmt/class.h5
-rw-r--r--drivers/platform/x86/intel/pmt/crashlog.c235
-rw-r--r--drivers/platform/x86/intel/pmt/discovery.c2
-rw-r--r--drivers/platform/x86/intel/pmt/telemetry.c3
-rw-r--r--drivers/platform/x86/intel/punit_ipc.c4
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.c57
-rw-r--r--drivers/platform/x86/intel/telemetry/core.c4
-rw-r--r--drivers/platform/x86/intel/tpmi_common.c822
-rw-r--r--drivers/platform/x86/intel/tpmi_common.h52
-rw-r--r--drivers/platform/x86/intel/tpmi_power_domains.c18
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c3
-rw-r--r--drivers/platform/x86/intel/vsec.c28
-rw-r--r--drivers/platform/x86/intel/vsec_tpmi.c822
-rw-r--r--drivers/platform/x86/intel/wmi/sbl-fw-update.c16
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c16
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.c14
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c499
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.c458
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.c61
-rw-r--r--drivers/platform/x86/lenovo/wmi-other.c24
-rw-r--r--drivers/platform/x86/lenovo/yogabook.c11
-rw-r--r--drivers/platform/x86/msi-ec.c12
-rw-r--r--drivers/platform/x86/msi-laptop.c2
-rw-r--r--drivers/platform/x86/msi-wmi.c41
-rw-r--r--drivers/platform/x86/oxpec.c75
-rw-r--r--drivers/platform/x86/panasonic-laptop.c14
-rw-r--r--drivers/platform/x86/samsung-laptop.c2
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc.c21
-rw-r--r--drivers/platform/x86/topstar-laptop.c6
-rw-r--r--drivers/platform/x86/toshiba_haps.c11
-rw-r--r--drivers/platform/x86/tuxedo/nb04/wmi_ab.c122
-rw-r--r--drivers/platform/x86/uniwill/Kconfig1
-rw-r--r--drivers/platform/x86/uniwill/uniwill-acpi.c573
-rw-r--r--drivers/platform/x86/uniwill/uniwill-wmi.h3
-rw-r--r--include/linux/intel_vsec.h19
-rw-r--r--include/linux/platform_data/x86/asus-wmi.h9
-rw-r--r--include/linux/string_choices.h6
-rw-r--r--include/linux/wmi.h3
-rw-r--r--include/uapi/linux/amd-pmf.h3
-rw-r--r--tools/power/x86/intel-speed-select/Makefile4
-rw-r--r--tools/power/x86/intel-speed-select/isst-config.c4
-rw-r--r--tools/power/x86/intel-speed-select/isst-core-mbox.c2
-rw-r--r--tools/power/x86/intel-speed-select/isst.h2
133 files changed, 6805 insertions, 2856 deletions
diff --git a/Documentation/ABI/removed/sysfs-driver-uniwill-laptop b/Documentation/ABI/removed/sysfs-driver-uniwill-laptop
new file mode 100644
index 000000000000..c226efc23b59
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-driver-uniwill-laptop
@@ -0,0 +1,10 @@
+What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation
+Date: November 2025
+KernelVersion: 6.19
+Contact: Armin Wolf <W_Armin@gmx.de>
+Description:
+ Forces the integrated lightbar to display a rainbow animation when the machine
+ is not suspended. Writing "1"/"0" into this file enables/disables this
+ functionality.
+
+ Reading this file returns the current status of the rainbow animation functionality.
diff --git a/Documentation/ABI/testing/debugfs-tpmi b/Documentation/ABI/testing/debugfs-tpmi
index c493a1403d2f..09212978fc41 100644
--- a/Documentation/ABI/testing/debugfs-tpmi
+++ b/Documentation/ABI/testing/debugfs-tpmi
@@ -25,9 +25,11 @@ Description:
Allows to write at any offset. It doesn't check for Read/Write access
as hardware will not allow to write at read-only memory. This write is
at offset multiples of 4. The format is instance,offset,contents.
-Example:
-echo 0,0x20,0xff > mem_write
-echo 1,64,64 > mem_write
+Example::
+
+ echo 0,0x20,0xff > mem_write
+ echo 1,64,64 > mem_write
+
Users: Debugging, any user space test suite
What: /sys/kernel/debug/tpmi-<n>/plr/domain<n>/status
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec
new file mode 100644
index 000000000000..80d01fc32abb
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec
@@ -0,0 +1,22 @@
+What: /sys/bus/i2c/devices/*/fn_lock
+Date: October 2026
+KernelVersion: 7.5
+Contact: Oleg Keri <okerixx@gmail.com>
+Description:
+ Control fn-lock mode.
+
+ * 1 -> Switched On
+ * 0 -> Switched Off
+
+ When switched on, the function keys act as F1..F12 and the
+ multimedia functions require the Fn modifier.
+
+ The embedded controller reports the Fn+Esc key as an event but
+ keeps no state of its own, so this attribute is the state: the
+ driver toggles it when the key is pressed, and writing it takes
+ effect immediately. A toggle from the key is signalled with
+ sysfs_notify(), so the attribute can be polled.
+
+ For example::
+
+ # echo "0" > /sys/bus/i2c/devices/9-0070/fn_lock
diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
index 943f92c6b561..f3060aaf8a76 100644
--- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
+++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
@@ -29,25 +29,14 @@ Description:
Reading this file returns the current enable status of the touchpad toggle
functionality.
-What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation
-Date: November 2025
-KernelVersion: 6.19
-Contact: Armin Wolf <W_Armin@gmx.de>
-Description:
- Forces the integrated lightbar to display a rainbow animation when the machine
- is not suspended. Writing "1"/"0" into this file enables/disables this
- functionality.
-
- Reading this file returns the current status of the rainbow animation functionality.
-
What: /sys/bus/platform/devices/INOU0000:XX/breathing_in_suspend
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Causes the integrated lightbar to display a breathing animation when the machine
- has been suspended and is running on AC power. Writing "1"/"0" into this file
- enables/disables this functionality.
+ has been suspended, is running on AC power and the lightbar brightness not zero.
+ Writing "1"/"0" into this file enables/disables this functionality.
Reading this file returns the current status of the breathing animation
functionality.
diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
index db4588af0278..60bf6868b849 100644
--- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
+++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
@@ -583,7 +583,8 @@ Events that are propagated by the driver to userspace:
0x6030 System thermal table changed
0x6032 Thermal Control command set completion (DYTC, Windows)
0x6040 Nvidia Optimus/AC adapter related (TO BE VERIFIED)
-0x60C0 X1 Yoga 2016, Tablet mode status changed
+0x60C0 X1 Yoga 2016 tablet mode or X1 Fold 16 Gen 1 keyboard
+ attachment state changed
0x60F0 Thermal Transformation changed (GMTS, Windows)
====== =====================================================
@@ -1658,6 +1659,29 @@ if damage detected:
The property is read-only. If feature is not supported then sysfs
attribute is not created.
+X1 Fold keyboard attachment
+---------------------------
+
+sysfs attribute: keyboard_attached_on_screen
+
+ThinkPad X1 Fold 16 Gen 1 systems can report whether the keyboard is
+currently attached on-screen.
+
+This attribute is currently supported on ThinkPad X1 Fold 16 Gen 1
+systems only.
+
+The command to check the keyboard attachment state is::
+
+ cat /sys/devices/platform/thinkpad_acpi/keyboard_attached_on_screen
+
+The values are:
+
+- 0 if the keyboard is not attached on-screen
+- 1 if the keyboard is attached on-screen
+
+The property is read-only. If feature is not supported then the sysfs
+attribute is not created.
+
Multiple Commands, Module Parameters
------------------------------------
diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst
index be50b45b82ef..d8ce30f10d7c 100644
--- a/Documentation/admin-guide/laptops/uniwill-laptop.rst
+++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst
@@ -72,17 +72,18 @@ Lightbar
--------
The ``uniwill-laptop`` driver exposes the lightbar found on some models as a standard multicolor
-LED class device. The default name of this LED class device is ``uniwill:multicolor:status``.
+LED class device. The default name of this LED class device is ``uniwill:rgb:status``.
-See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various
-animation modes of the lightbar.
+The lightbar supports a separate rainbow animation mode that can be enabled by selecting the
+`uniwill-rainbow` LED trigger. To enable a breathing animation during suspend, see
+Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
Keyboard Backlight
------------------
The ``uniwill-laptop`` driver supports controlling the keyboard backlight using the standard
LED class interface. The default name of this LED class device is ``uniwill:white:kbd_backlight``
-when the keyboard backlight supports only a single color, or ``uniwill:multicolor:kbd_backlight``
+when the keyboard backlight supports only a single color, or ``uniwill:rgb:kbd_backlight``
when the keyboard backlight supports RGB colors. The maximum intensity for each color channel
in RGB mode is 50.
diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst
index fa1fc240e212..c483c68dcfad 100644
--- a/Documentation/arch/x86/amd_hsmp.rst
+++ b/Documentation/arch/x86/amd_hsmp.rst
@@ -8,6 +8,13 @@ Newer Fam19h(model 0x00-0x1f, 0x30-0x3f, 0x90-0x9f, 0xa0-0xaf),
Fam1Ah(model 0x00-0x1f) EPYC server line of processors from AMD support
system management functionality via HSMP (Host System Management Port).
+The Fam1Ah(model 0x80-0x8f, 0xe0-0xe3) client line of processors is
+supported as well. Those models share one mailbox and speak the Ryzen
+Master SMC message set instead of the server HSMP message set, so the
+message IDs accepted on them are the HSMP_CLIENT_* ones listed in
+arch/x86/include/uapi/asm/amd_hsmp.h. The character device and ioctl
+interface described below are the same.
+
The Host System Management Port (HSMP) is an interface to provide
OS-level software with access to system management functions via a
set of mailbox registers.
@@ -17,7 +24,8 @@ More details on the interface can be found in chapter
Eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50
-HSMP interface is supported on EPYC line of server CPUs and MI300A (APU).
+HSMP interface is supported on EPYC line of server CPUs, MI300A (APU) and
+the Fam1Ah client models listed above.
HSMP device
@@ -213,9 +221,23 @@ The following IOCTLs are defined:
``sizeof(struct hsmp_metric_table)``. Later version metrics table
layout is documented in the Public PPR.
+ The Family 1Ah client models carry their own telemetry table instead,
+ described in `Family 1Ah client telemetry table`_ below.
+
The ioctl would return a non-zero on failure; you can read errno to see
what happened. The transaction returns 0 on success.
+Family 1Ah client telemetry table
+=================================
+On the Family 1Ah client models, ``HSMP_CLIENT_GET_METRICS_TABLE`` (05h)
+returns the Ryzen Master SMC telemetry table, table version 5 as
+reported by ``HSMP_CLIENT_GET_METRICS_TABLE_VER`` (04h). This is the
+Telemetry Table RM layout, defined as ``struct hsmp_telemetry_table_rm``
+in amd_hsmp.h. The driver copies the table out without interpreting it.
+
+See the telemetry table chapter of the AMD Public PPR for the model in
+use for the units and encodings of its fields.
+
More details on the interface and message definitions can be found in chapter
"7 Host System Management Port (HSMP)" of the respective family/model PPR
eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50
diff --git a/Documentation/wmi/devices/acer-wmi-battery.rst b/Documentation/wmi/devices/acer-wmi-battery.rst
new file mode 100644
index 000000000000..8aa3e1334490
--- /dev/null
+++ b/Documentation/wmi/devices/acer-wmi-battery.rst
@@ -0,0 +1,70 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+====================================================
+Acer WMI Battery interface driver (acer-wmi-battery)
+====================================================
+
+Introduction
+============
+
+Some Acer laptops support setting battery charge control limits and allow a
+user to calibrate their battery.
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+ [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Class used to control smart battery, Version 2.88"), guid("{79772EC5-04B1-4bfd-843C-61E7F77B6CC9}")]
+ class BatteryControl {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiMethodId(19), Implemented, read, write, Description("Get battery Information Interface.")] void GetBattInfoInterface([in] uint32 uBatteryInfoIndex, [in] uint32 uBatteryNo, [out] uint32 uReturn);
+ [WmiMethodId(20), Implemented, read, write, Description("Get Battery Health Control Status.")] void GetBatteryHealthControlStatus([in] uint8 uBatteryNo, [in] uint8 uFunctionQuery, [in] uint8 uReserved[2], [out] uint8 uFunctionList, [out] uint8 uReturn[2], [out] uint8 uFunctionStatus[5]);
+ [WmiMethodId(21), Implemented, read, write, Description("Set Battery Health Control.")] void SetBatteryHealthControl([in] uint8 uBatteryNo, [in] uint8 uFunctionMask, [in] uint8 uFunctionStatus, [in] uint8 uReservedIn[5], [out] uint16 uReturn, [out] uint16 uReservedOut);
+ [WmiMethodId(22), Implemented, read, write, Description("Get Battery Function Data.")] void GetBatteryFunctionData([in] uint8 uFunctionMask, [in] uint8 uReservedIn[3], [out] uint8 uReturnCode[2], [out] uint8 uBACStartTime[2], [out] uint8 uBACStopTime[2], [out] uint8 uBACStatus, [out] uint8 uReservedOut[9]);
+ [WmiMethodId(23), Implemented, read, write, Description("Set Battery Function Data.")] void SetBatteryFunctionData([in] uint8 uFunctionMask, [in] uint8 uBACSwitch, [in] uint8 uReservedIn[6], [out] uint16 uReturnCode, [out] uint8 uReservedOut[2]);
+ };
+
+For all methods the ``uBatteryNo`` is always ``0x1``, there are no known Acer
+laptop models with two batteries.
+
+WMI method GetBatteryInfoInterface()
+------------------------------------
+
+Returns additional battery information, the data seems to be based on the
+"Smart Battery Data Specification". The only known value for
+``uBatteryInfoIndex`` is ``0x8``, the battery temperature.
+
+WMI method GetBatteryHealthControlStatus()
+------------------------------------------
+
+This interface does not exist on all Acer laptops, it returns whether battery
+charge limiting or calibration mode is enabled.
+
+When calling this method, ``uFunctionList`` is a bitmask with known values:
+
++--------------------+------------------------------------+
+| Value | Description |
++====================+====================================+
+| 0x01 | Health mode supported |
++--------------------+------------------------------------+
+| 0x02 | Calibration mode supported |
++--------------------+------------------------------------+
+
+``uFunctionStatus`` is a 5 byte array known elements are ``0`` for health mode
+and ``1`` for calibration mode. A non-zero value means the mode is enabled.
+
+WMI method SetBatteryHealthControl()
+------------------------------------
+
+This interface does not exist on all Acer laptops, it enables battery
+charge limiting or battery calibration.
+
+The argument ``uFunctionMask`` is known to accept either ``0x01`` for setting
+health mode or ``0x02`` for calibration mode; other values are unknown.
+``uFunctionStatus`` is an uint8 value, a non-zero value enables the given mode.
diff --git a/Documentation/wmi/devices/bitland-mifs-wmi.rst b/Documentation/wmi/devices/bitland-mifs-wmi.rst
index 9e86ecc2993c..27c3fd248279 100644
--- a/Documentation/wmi/devices/bitland-mifs-wmi.rst
+++ b/Documentation/wmi/devices/bitland-mifs-wmi.rst
@@ -67,7 +67,7 @@ WMI Methods (MICommonInterface)
The ``MICommonInterface`` class (GUID: ``{b60bfb48-3e5b-49e4-a0e9-8cffe1b3434b}``)
is the primary control interface. It uses a 32-byte buffer for both input
-(``InData``) and output (``OutData``).
+(``InData``) and output (``OutData`` + ``Reserved``).
Method Structure
----------------
@@ -77,53 +77,78 @@ The data packet follows a standardized format:
+----------+------------------------------------------------------------------+
| Byte | Description |
+==========+==================================================================+
-| 1 | Method Type: Get (0xFA / 250) or Set (0xFB / 251) |
+| 1 and 2 | Method Type or Return Code |
+----------+------------------------------------------------------------------+
-| 3 | Command ID (Method Name) |
+| 3 and 4 | Command ID (Method Name) |
+----------+------------------------------------------------------------------+
-| 4 - 31 | Arguments (for Set) or Return Data (for Get) |
+| 5 - 32 | Arguments (for Set) or Return Data (for Get) |
+----------+------------------------------------------------------------------+
+Method Types
+------------
+
+The following Method types are understood by the underlying firmware:
+
++--------+---------+
+| Type | Meaning |
++========+=========+
+| 0xFA00 | Read |
++--------+---------+
+| 0xFB00 | Write |
++--------+---------+
+
+Return Codes
+------------
+
+The following Return Codes are know to be returned in response to a WMI method invocation:
+
++--------+--------------------+
+| Code | Meaning |
++========+====================+
+| 0x8000 | Success |
++--------+--------------------+
+| 0xE000 | Invalid Command ID |
++--------+--------------------+
Command IDs
-----------
-The following Command IDs are used in the third byte of the buffer:
+The following Command IDs know to be used on some models:
+----------+-----------------------+------------------------------------------+
| ID | Name | Values / Description |
+==========+=======================+==========================================+
-| 8 | SystemPerMode | 0: Balance, 1: Performance, 2: Quiet, |
+| 0x0800 | SystemPerMode | 0: Balance, 1: Performance, 2: Quiet, |
| | | 3: Full-speed |
+----------+-----------------------+------------------------------------------+
-| 9 | GPUMode | 0: Hybrid, 1: Discrete, 2: UMA |
+| 0x0900 | GPUMode | 0: Hybrid, 1: Discrete, 2: UMA |
+----------+-----------------------+------------------------------------------+
-| 10 | KeyboardType | 0: White, 1: Single RGB, 2: Zone RGB |
+| 0x0A00 | KeyboardType | 0: White, 1: Single RGB, 2: Zone RGB |
+----------+-----------------------+------------------------------------------+
-| 11 | FnLock | 0: Off, 1: On |
+| 0x0B00 | FnLock | 0: Off, 1: On |
+----------+-----------------------+------------------------------------------+
-| 12 | TPLock | 0: Unlock, 1: Lock (Touchpad) |
+| 0x0C00 | TPLock | 0: Unlock, 1: Lock (Touchpad) |
+----------+-----------------------+------------------------------------------+
-| 13 | CPUGPUSYSFanSpeed | Returns 12 bytes of fan data: |
+| 0x0D00 | CPUGPUSYSFanSpeed | Returns 12 bytes of fan data: |
| | | Bytes 4-5: CPU Fan RPM (Little Endian) |
| | | Bytes 6-7: GPU Fan RPM (Little Endian) |
| | | Bytes 10-11: SYS Fan RPM (Little Endian) |
+----------+-----------------------+------------------------------------------+
-| 16 | RGBKeyboardMode | 0: Off, 1: Auto Cyclic, 2: Fixed, |
+| 0x1000 | RGBKeyboardMode | 0: Off, 1: Auto Cyclic, 2: Fixed, |
| | | 3: Custom |
+----------+-----------------------+------------------------------------------+
-| 17 | RGBKeyboardColor | Bytes 4, 5, 6: Red, Green, Blue values |
+| 0x1100 | RGBKeyboardColor | Bytes 4, 5, 6: Red, Green, Blue values |
+----------+-----------------------+------------------------------------------+
-| 18 | RGBKeyboardBrightness | 0-10: Brightness Levels, 128: Auto |
+| 0x1200 | RGBKeyboardBrightness | 0-10: Brightness Levels, 128: Auto |
+----------+-----------------------+------------------------------------------+
-| 19 | SystemAcType | 1: Type-C, 2: Circular Hole (DC) |
+| 0x1300 | SystemAcType | 1: Type-C, 2: Circular Hole (DC) |
+----------+-----------------------+------------------------------------------+
-| 20 | MaxFanSpeedSwitch | Byte 4: Fan Type (0: CPU/GPU, 1: SYS) |
+| 0x1400 | MaxFanSpeedSwitch | Byte 4: Fan Type (0: CPU/GPU, 1: SYS) |
| | | Byte 5: State (0: Off, 1: On) |
+----------+-----------------------+------------------------------------------+
-| 21 | MaxFanSpeed | Sets manual fan speed duty cycle |
+| 0x1500 | MaxFanSpeed | Sets manual fan speed duty cycle |
+----------+-----------------------+------------------------------------------+
-| 22 | CPUThermometer | Returns CPU Temperature |
+| 0x1600 | CPUThermometer | Returns CPU Temperature |
+----------+-----------------------+------------------------------------------+
WMI Events (HID_EVENT20)
@@ -189,7 +214,8 @@ Performance Modes
Changing the performance mode via Command ID 0x08 (SystemPerMode) affects the
power limits (PL1/PL2) and fan curves managed by the Embedded Controller (EC).
Note that the "Full-speed" and "Performance" mode (1, 3) is typically only
-available when the system is connected to a DC power source (not USB-C/PD).
+available when the system is connected to a DC power source (not USB-C/PD on
+model with a dedicated DC connector).
In the driver implementation, switch to performance/full-speed mode without
DC power connected will throw the EOPNOTSUPP error.
diff --git a/MAINTAINERS b/MAINTAINERS
index 81fcd0bb9dbe..748ce4d1c6a2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13276,9 +13276,8 @@ S: Maintained
F: drivers/platform/x86/intel/atomisp2/led.c
INTEL BIOS SAR INT1092 DRIVER
-M: Shravan Sudhakar <s.shravan@intel.com>
L: platform-driver-x86@vger.kernel.org
-S: Maintained
+S: Orphan
F: drivers/platform/x86/intel/int1092/
INTEL BROXTON PMC DRIVER
@@ -13800,6 +13799,7 @@ M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/debugfs-tpmi
+F: drivers/platform/x86/intel/tpmi_common.[ch]
F: drivers/platform/x86/intel/vsec_tpmi.c
F: include/linux/intel_tpmi.h
@@ -15122,6 +15122,7 @@ F: drivers/usb/misc/legousbtower.c
LENOVO drivers
M: Mark Pearson <mpearson-lenovo@squebb.ca>
M: Derek J. Clark <derekjohn.clark@gmail.com>
+M: Rong Zhang <i@rong.moe>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
@@ -28517,6 +28518,7 @@ F: tools/testing/selftests/turbostat/
TUXEDO DRIVERS
M: Werner Sembach <wse@tuxedocomputers.com>
+M: Aaron Erhardt <aer@tuxedocomputers.com>
L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/x86/tuxedo/
@@ -28755,6 +28757,7 @@ UNIWILL LAPTOP DRIVER
M: Armin Wolf <W_Armin@gmx.de>
L: platform-driver-x86@vger.kernel.org
S: Maintained
+F: Documentation/ABI/removed/sysfs-driver-uniwill-laptop
F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop
F: Documentation/admin-guide/laptops/uniwill-laptop.rst
F: Documentation/wmi/devices/uniwill-laptop.rst
@@ -30127,7 +30130,7 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/stable/sysfs-platform-wmi-bmof
F: Documentation/wmi/devices/wmi-bmof.rst
-F: drivers/platform/x86/wmi-bmof.c
+F: drivers/platform/wmi/bmof.c
WOLFSON MICROELECTRONICS DRIVERS
L: patches@opensource.cirrus.com
diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h
index eda336bfd3e9..dcbe89dc4d2b 100644
--- a/arch/x86/include/uapi/asm/amd_hsmp.h
+++ b/arch/x86/include/uapi/asm/amd_hsmp.h
@@ -10,6 +10,12 @@
#define HSMP_MAX_MSG_LEN 8
/*
+ * HSMP client platforms (Ryzen Master SMC) accept at most four input
+ * argument words and four output/response words per message.
+ */
+#define HSMP_CLIENT_MAX_MSG_LEN 4
+
+/*
* HSMP Messages supported
*/
enum hsmp_message_ids {
@@ -71,6 +77,11 @@ enum hsmp_message_ids {
HSMP_MSG_ID_MAX,
};
+/*
+ * The driver validates num_args and response_sz before sending a message.
+ * HSMP client platforms are bound to HSMP_CLIENT_MAX_MSG_LEN in each
+ * direction; server platforms are bound per-message by hsmp_msg_desc_table[].
+ */
struct hsmp_message {
__u32 msg_id; /* Message ID */
__u16 num_args; /* Number of input argument words in message */
@@ -95,6 +106,15 @@ enum hsmp_proto_versions {
HSMP_PROTO_VER7
};
+/*
+ * Ryzen Master SMC interface version, reported by
+ * HSMP_CLIENT_GET_INTERFACE_VER. Separate numbering space from enum
+ * hsmp_proto_versions, which applies to the server set only.
+ */
+enum ryzen_master_proto_versions {
+ RYZEN_MASTER_PROTO_VER1 = 1,
+};
+
struct hsmp_msg_desc {
int num_args;
int response_sz;
@@ -664,4 +684,404 @@ struct hsmp_telemetry_data {
#define HSMP_IOCTL_GET_TELEMETRY_DATA \
_IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data)
+/**
+ * enum hsmp_client_message_ids - Ryzen Master SMC (RMSMC) message IDs
+ * @HSMP_CLIENT_TEST: 01h. Test message. input: args[0] = xx. output:
+ * args[0] = xx + 1.
+ * @HSMP_CLIENT_GET_SMU_VER: 02h. Get MP1 firmware version. output:
+ * args[0] = MP1 firmware version.
+ * @HSMP_CLIENT_GET_INTERFACE_VER: 03h. Get interface version. output:
+ * args[0] = interface version.
+ * @HSMP_CLIENT_GET_METRICS_TABLE_VER: 04h. Get metrics table version.
+ * output: args[0] = metrics table version.
+ * @HSMP_CLIENT_GET_METRICS_TABLE: 05h. Get metrics table. No arguments.
+ * Success means firmware has written the metrics table to the DRAM
+ * address reported by @HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR.
+ * @HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR: 06h. Get metrics table DRAM
+ * address. output: args[0] = address[31:0], args[1] = address[63:32],
+ * args[2] = table size in bytes.
+ * @HSMP_CLIENT_SET_CORE_PSM_MARGIN: 07h. Set core voltage margin. input:
+ * args[0] = ccx number[31:28] + core number[27:20] + reserved[19:16] +
+ * margin in mV[15:0].
+ * @HSMP_CLIENT_SET_ALL_CORE_PSM_MARGIN: 08h. Set voltage margin for all
+ * cores. input: args[0] = margin in mV[15:0].
+ * @HSMP_CLIENT_SET_FAST_PPT_LIMIT: 09h. Set APU fast PPT limit. input:
+ * args[0] = limit in mW.
+ * @HSMP_CLIENT_SET_VRM_VDD_CURRENT_LIMIT: 0Ah. Set VDDCR_VDD TDC. input:
+ * args[0] = limit in mA.
+ * @HSMP_CLIENT_SET_VRM_VDD_MAX_CURRENT_LIMIT: 0Bh. Set VDDCR_VDD EDC.
+ * input: args[0] = limit in mA.
+ * @HSMP_CLIENT_SET_TJ_MAX: 0Ch. Set maximum junction temperature. input:
+ * args[0] = temperature in degrees C.
+ * @HSMP_CLIENT_SET_FIT_LIMIT_SCALAR: 0Dh. Set failures-in-time limit
+ * scalar. input: args[0] = scalar (0 to 100).
+ * @HSMP_CLIENT_ENABLE_OVERCLOCKING: 0Eh. Enable overclocking. No
+ * arguments.
+ * @HSMP_CLIENT_DISABLE_OVERCLOCKING: 0Fh. Disable overclocking. No
+ * arguments.
+ * @HSMP_CLIENT_SET_OVERCLOCK_FREQ_ALL_CORES: 10h. Set all-core overclock
+ * frequency. input: args[0] = frequency in MHz[15:0].
+ * @HSMP_CLIENT_SET_OVERCLOCK_FREQ_PER_CORE: 11h. Set per-core overclock
+ * frequency. input: args[0] = ccd number[31:28] + ccx number[27:24] +
+ * core number[23:20] + frequency in MHz[19:0].
+ * @HSMP_CLIENT_SET_OVERCLOCK_VID: 12h. Set overclock VID. input:
+ * args[0] = reserved[31:17] + vid[16:8] + pstate[7:0].
+ * @HSMP_CLIENT_SPARE_0X13: 13h. Reserved.
+ * @HSMP_CLIENT_GET_CORE_PERF_ORDER: 14h. Get core performance order.
+ * input: args[0] = core performance rank index[31:0] (0 to Enabled
+ * cores-1). output: args[0] = voltage in mV[31:21] + frequency in
+ * MHz[20:9] + fll[8:6] + core number[5:2] + ccx number[1:0].
+ * @HSMP_CLIENT_SET_SUSTAINED_POWER_LIMIT: 15h. Set SOC sustained power
+ * limit. input: args[0] = limit in mW.
+ * @HSMP_CLIENT_SET_SLOW_PPT_LIMIT: 16h. Set APU slow PPT limit. input:
+ * args[0] = limit in mW.
+ * @HSMP_CLIENT_SET_VRM_GFX_MAX_CURRENT_LIMIT: 17h. Set VDDCR_GFX EDC.
+ * input: args[0] = limit in mA.
+ * @HSMP_CLIENT_SET_VRM_SOC_CURRENT_LIMIT: 18h. Set VDDCR_SOC TDC. input:
+ * args[0] = limit in mA.
+ * @HSMP_CLIENT_SET_FAST_SPM_LIMIT: 19h. Set fast SPM limit. input:
+ * args[0] = limit in mW.
+ * @HSMP_CLIENT_SET_SLOW_SPM_LIMIT: 1Ah. Set slow SPM limit. input:
+ * args[0] = limit in mW.
+ * @HSMP_CLIENT_GET_CORE_PSM_MARGIN: 1Bh. Get core voltage margin. input:
+ * args[0] = ccx number[31:28] + core number[27:20] + reserved[19:0].
+ * output: args[0] = ccx number[31:28] + core number[27:20] +
+ * reserved[19:16] + margin in mV[15:0].
+ * @HSMP_CLIENT_GET_GFX_PSM_MARGIN: 1Ch. Get graphics voltage margin.
+ * output: args[0] = margin in mV[15:0].
+ * @HSMP_CLIENT_SPARE_0X1D: 1Dh. Reserved.
+ * @HSMP_CLIENT_SPARE_0X1E: 1Eh. Reserved.
+ * @HSMP_CLIENT_SPARE_0X1F: 1Fh. Reserved.
+ * @HSMP_CLIENT_SPARE_0X20: 20h. Reserved.
+ * @HSMP_CLIENT_SET_GFXCLK_OVERDRIVE_BY_FREQ_VID: 21h. Set GfxClk
+ * overdrive by frequency/VID. input: args[0] = reserved[31:25] +
+ * vid[24:16] + frequency in MHz[15:0].
+ * @HSMP_CLIENT_DISABLE_GFXCLK_OVERDRIVE: 22h. Disable GfxClk overdrive.
+ * No arguments.
+ * @HSMP_CLIENT_SET_GFX_PSM_MARGIN: 23h. Set graphics voltage margin.
+ * input: args[0] = margin in mV[15:0].
+ * @HSMP_CLIENT_SET_CCLK_FMAX_OFFSET: 24h. Set CCLK Fmax offset. input:
+ * args[0] = maximum frequency in MHz[15:0].
+ * @HSMP_CLIENT_SET_CORE_POWER_LIMIT_OFFSET: 25h. Set core power limit
+ * offset. input: args[0] = limit in mW.
+ * @HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND: 26h. Add extra core PSM
+ * guardband. input: args[0] = PsmGuardband[2][31:24] +
+ * PsmGuardband[1][23:16] + PsmGuardband[0][15:8] + TriggerUpdate[7] +
+ * AvfsType[6:4] + frequency index[3:0].
+ * @HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND_GFX: 27h. Add extra graphics PSM
+ * guardband. input: args[0] = PsmGuardband[2][31:24] +
+ * PsmGuardband[1][23:16] + PsmGuardband[0][15:8] + TriggerUpdate[7] +
+ * frequency index[6:0].
+ * @HSMP_CLIENT_SET_GFXCLK_FMAX: 28h. Set GfxClk Fmax. input: args[0] =
+ * maximum frequency in MHz[15:0].
+ * @HSMP_CLIENT_MSG_ID_MAX: Number of message IDs, not a valid ID itself.
+ *
+ * Message IDs accepted on the Family 1Ah client platforms, Models 80h-8Fh
+ * and E0h-E3h. These parts drive one mailbox and speak the Ryzen Master
+ * SMC message set instead of the server HSMP message set enumerated in
+ * &enum hsmp_message_ids. Not all platforms support all messages; consult
+ * the supported list of messages in the HSMP chapter of the respective
+ * family/model PPR. Unsupported messages return -ENOMSG.
+ */
+enum hsmp_client_message_ids {
+ HSMP_CLIENT_TEST = 1,
+ HSMP_CLIENT_GET_SMU_VER,
+ HSMP_CLIENT_GET_INTERFACE_VER,
+ HSMP_CLIENT_GET_METRICS_TABLE_VER,
+ HSMP_CLIENT_GET_METRICS_TABLE,
+ HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR,
+ HSMP_CLIENT_SET_CORE_PSM_MARGIN,
+ HSMP_CLIENT_SET_ALL_CORE_PSM_MARGIN,
+ HSMP_CLIENT_SET_FAST_PPT_LIMIT,
+ HSMP_CLIENT_SET_VRM_VDD_CURRENT_LIMIT,
+ HSMP_CLIENT_SET_VRM_VDD_MAX_CURRENT_LIMIT,
+ HSMP_CLIENT_SET_TJ_MAX,
+ HSMP_CLIENT_SET_FIT_LIMIT_SCALAR,
+ HSMP_CLIENT_ENABLE_OVERCLOCKING,
+ HSMP_CLIENT_DISABLE_OVERCLOCKING,
+ HSMP_CLIENT_SET_OVERCLOCK_FREQ_ALL_CORES,
+ HSMP_CLIENT_SET_OVERCLOCK_FREQ_PER_CORE,
+ HSMP_CLIENT_SET_OVERCLOCK_VID,
+ HSMP_CLIENT_SPARE_0X13,
+ HSMP_CLIENT_GET_CORE_PERF_ORDER,
+ HSMP_CLIENT_SET_SUSTAINED_POWER_LIMIT,
+ HSMP_CLIENT_SET_SLOW_PPT_LIMIT,
+ HSMP_CLIENT_SET_VRM_GFX_MAX_CURRENT_LIMIT,
+ HSMP_CLIENT_SET_VRM_SOC_CURRENT_LIMIT,
+ HSMP_CLIENT_SET_FAST_SPM_LIMIT,
+ HSMP_CLIENT_SET_SLOW_SPM_LIMIT,
+ HSMP_CLIENT_GET_CORE_PSM_MARGIN,
+ HSMP_CLIENT_GET_GFX_PSM_MARGIN,
+ HSMP_CLIENT_SPARE_0X1D,
+ HSMP_CLIENT_SPARE_0X1E,
+ HSMP_CLIENT_SPARE_0X1F,
+ HSMP_CLIENT_SPARE_0X20,
+ HSMP_CLIENT_SET_GFXCLK_OVERDRIVE_BY_FREQ_VID,
+ HSMP_CLIENT_DISABLE_GFXCLK_OVERDRIVE,
+ HSMP_CLIENT_SET_GFX_PSM_MARGIN,
+ HSMP_CLIENT_SET_CCLK_FMAX_OFFSET,
+ HSMP_CLIENT_SET_CORE_POWER_LIMIT_OFFSET,
+ HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND,
+ HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND_GFX,
+ HSMP_CLIENT_SET_GFXCLK_FMAX,
+ HSMP_CLIENT_MSG_ID_MAX,
+};
+
+#define HSMP_TELEMETRY_RM_MAX_CCX 4
+#define HSMP_TELEMETRY_RM_CORES_PER_CCX 12
+#define HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE 8
+#define HSMP_TELEMETRY_RM_NPU_BUSY_DOMAINS 3
+#define HSMP_TELEMETRY_RM_OC_DOMAINS 5
+#define HSMP_TELEMETRY_RM_OC_SUBDOMAINS 5
+#define HSMP_TELEMETRY_RM_OC_GUARDBANDS 3
+
+/* Firmware packs this table to a 4-byte boundary */
+#pragma pack(push, 4)
+
+/*
+ * struct hsmp_telemetry_table_rm_iod - I/O die counters for RM telemetry.
+ *
+ * Accumulator (*_acc) fields are monotonic counters; consumers derive
+ * deltas across two snapshots and normalise by accumulation_counter.
+ */
+struct hsmp_telemetry_table_rm_iod {
+ __u32 accumulation_counter;
+
+ /* SET VOLTAGES */
+ __u64 vddcr_set_voltage;
+ __u64 vddcr_soc_set_voltage;
+ __u64 vddcr_npu_set_voltage;
+ __u64 vddcr_lp_set_voltage;
+ __u64 vddcr_gfx_set_voltage;
+ __u64 vdd_misc_set_voltage;
+
+ /* TELEMETRY VOLTAGES */
+ __u64 vddcr_telemetry_voltage;
+ __u64 vddcr_soc_telemetry_voltage;
+ __u64 vddcr_npu_telemetry_voltage;
+ __u64 vddcr_lp_telemetry_voltage;
+ __u64 vddcr_gfx_telemetry_voltage;
+ __u64 vdd_misc_telemetry_voltage;
+
+ /* TELEMETRY POWERS */
+ __u64 vddcr_telemetry_power;
+ __u64 vddcr_soc_telemetry_power;
+ __u64 vddcr_npu_telemetry_power;
+ __u64 vddcr_lp_telemetry_power;
+ __u64 vddcr_gfx_telemetry_power;
+ __u64 vdd_misc_telemetry_power;
+
+ /* THROTTLERS - FAST PPT */
+ __u32 fppt_fused_limit;
+ __u32 fppt_max_irm_limit;
+ __u32 fppt_max_pbo_limit;
+ __u32 fppt_limit;
+ __u64 fppt_value_acc;
+ __u32 fppt_residency_acc;
+
+ /* THROTTLERS - SLOW PPT */
+ __u32 sppt_fused_limit;
+ __u32 sppt_max_irm_limit;
+ __u32 sppt_max_pbo_limit;
+ __u32 sppt_limit;
+ __u64 sppt_value_acc;
+ __u32 sppt_residency_acc;
+
+ /* THROTTLERS - SPL */
+ __u32 spl_fused_limit;
+ __u32 spl_max_irm_limit;
+ __u32 spl_max_pbo_limit;
+ __u32 spl_limit;
+ __u64 spl_value_acc;
+ __u32 spl_residency_acc;
+
+ /* THROTTLERS - TDC VDDCR */
+ __u32 tdc_vddcr_fused_limit;
+ __u32 tdc_vddcr_max_irm_limit;
+ __u32 tdc_vddcr_max_pbo_limit;
+ __u32 tdc_vddcr_limit;
+ __u64 tdc_vddcr_value_acc;
+ __u32 tdc_vddcr_residency_acc;
+
+ /* THROTTLERS - TDC VDDCR SOC */
+ __u32 tdc_vddcr_soc_fused_limit;
+ __u32 tdc_vddcr_soc_max_irm_limit;
+ __u32 tdc_vddcr_soc_max_pbo_limit;
+ __u32 tdc_vddcr_soc_limit;
+ __u64 tdc_vddcr_soc_value_acc;
+ __u32 tdc_vddcr_soc_residency_acc;
+
+ /* THROTTLERS - TDC VDDCR NPU */
+ __u32 tdc_vddcr_npu_fused_limit;
+ __u32 tdc_vddcr_npu_max_irm_limit;
+ __u32 tdc_vddcr_npu_max_pbo_limit;
+ __u32 tdc_vddcr_npu_limit;
+ __u64 tdc_vddcr_npu_value_acc;
+ __u32 tdc_vddcr_npu_residency_acc;
+
+ /* THROTTLERS - TDC VDDCR LP */
+ __u32 tdc_vddcr_lp_fused_limit;
+ __u32 tdc_vddcr_lp_max_irm_limit;
+ __u32 tdc_vddcr_lp_max_pbo_limit;
+ __u32 tdc_vddcr_lp_limit;
+ __u64 tdc_vddcr_lp_value_acc;
+ __u32 tdc_vddcr_lp_residency_acc;
+
+ /* THROTTLERS - TDC VDDCR GFX */
+ __u32 tdc_vddcr_gfx_fused_limit;
+ __u32 tdc_vddcr_gfx_max_irm_limit;
+ __u32 tdc_vddcr_gfx_max_pbo_limit;
+ __u32 tdc_vddcr_gfx_limit;
+ __u64 tdc_vddcr_gfx_value_acc;
+ __u32 tdc_vddcr_gfx_residency_acc;
+
+ /* THROTTLERS - EDC VDDCR */
+ __u32 edc_vddcr_fused_limit;
+ __u32 edc_vddcr_max_irm_limit;
+ __u32 edc_vddcr_max_pbo_limit;
+ __u32 edc_vddcr_limit;
+
+ /* THROTTLERS - THERMAL */
+ __u32 thm_fused_limit;
+ __u32 thm_limit;
+ __u64 thm_value_acc;
+ __u32 thm_residency_acc;
+ __u32 prochot_residency_acc;
+ __u64 gfx_temp_acc;
+ __u64 soc_temp_acc;
+ __u32 p3t_fused_limit;
+ __u64 p3t_value_acc;
+
+ /* POWER */
+ __u64 system_power_acc;
+ __u64 apu_power_acc;
+ __u64 dgpu_power_acc;
+ __u64 npu_power_acc;
+
+ /* FREQUENCIES */
+ __u64 fclk_freq_eff_acc;
+ __u64 memclk_freq_eff_acc;
+ __u64 lclk_freq_eff_acc;
+ __u64 gfxclk_freq_eff_acc;
+ __u64 socclk_freq_eff_acc;
+ __u64 vclk_freq_eff_acc;
+ __u64 vpeclk_freq_eff_acc;
+ __u64 aieclk_freq_eff_acc;
+ __u64 npuhclk_freq_eff_acc;
+
+ /* BANDWIDTH */
+ __u64 dram_read_bandwidth;
+ __u64 dram_write_bandwidth;
+
+ /* ACTIVITY MONITORS */
+ __u64 gfx_busy_acc;
+ __u64 vcn_busy_acc;
+ __u64 npu_busy_acc[HSMP_TELEMETRY_RM_NPU_BUSY_DOMAINS];
+
+ /* STT (Skin Temperature Tracking) */
+ __u32 stt_min_limit;
+ __u64 stt_apu_hot_spot_temp_acc;
+ __u64 stt_hs2_hot_spot_temp_acc;
+ __u32 stt_apu_temp_limit;
+ __u64 stt_apu_skin_temp_acc;
+
+ /* RESIDENCIES - per-CCX CPU off */
+ __u64 cpu_off_residency[HSMP_TELEMETRY_RM_MAX_CCX];
+
+ /* DF P-STATES */
+ __u32 fclk_freq_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
+ __u32 uclk_freq_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
+ __u32 ddr_rate_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
+ __u8 df_pstate_source[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
+
+ /* SYSTEM */
+ __u8 gfx_disabled;
+ __u8 spare2[3];
+ __u32 gfxclk_fmax;
+ __u8 cclk_core_fuse_enable[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u8 cclk_core_enabled[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u32 cclk_fmax[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
+
+ /* OVERCLOCK CAPABLE */
+ __u8 cpu_precise_and_direct_oc_capable;
+ __u8 gfx_precise_and_direct_oc_capable;
+ __u8 pbo_basic_oc_capable;
+ __u8 pbo_advanced_oc_capable;
+ __u8 pbo_nitro_oc_capable;
+ __u8 memory_and_fabric_oc_capable;
+ __u8 misc_oc_capable;
+ __u8 extreme_cold_oc_capable;
+ __u8 down_config_control_capable;
+ __u8 spare0[3];
+
+ /* OVERCLOCK STATUS */
+ __u32 fit_limit_scalar;
+ __u8 ln2_enabled;
+ __u8 cpu_precise_and_direct_oc_enabled;
+ __u8 gfx_precise_and_direct_oc_enabled;
+ __u8 spare1[2];
+ __s8 psm_guardband[HSMP_TELEMETRY_RM_OC_DOMAINS]
+ [HSMP_TELEMETRY_RM_OC_SUBDOMAINS]
+ [HSMP_TELEMETRY_RM_OC_GUARDBANDS];
+ __s32 core_power_limit_offset;
+ __u32 max_freq_offset[HSMP_TELEMETRY_RM_OC_DOMAINS];
+
+ __u64 npu_temp_acc;
+ __u64 df_pstate_residency_acc[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
+ __u32 cclk_fboost;
+
+ /* PMF */
+ __u32 pmf_fast_apu_ppt_limit;
+ __u64 pmf_fast_apu_ppt_value_acc;
+ __u32 pmf_fast_apu_ppt_residency_acc;
+
+ __u32 pmf_slow_apu_ppt_limit;
+ __u64 pmf_slow_apu_ppt_value_acc;
+ __u32 pmf_slow_apu_ppt_residency_acc;
+
+ __u32 pmf_fast_spm_limit;
+ __u64 pmf_fast_spm_value_acc;
+ __u32 pmf_fast_spm_residency_acc;
+
+ __u32 pmf_slow_spm_limit;
+ __u64 pmf_slow_spm_value_acc;
+ __u32 pmf_slow_spm_residency_acc;
+
+ __u32 spare3[5];
+};
+
+/*
+ * struct hsmp_telemetry_table_rm_ccx - Per-CCX core counters for RM telemetry.
+ *
+ * Arrays are sized for the maximum cores per CCX; use the
+ * cclk_core_enabled[] mask in struct hsmp_telemetry_table_rm_iod to tell
+ * which entries are valid.
+ */
+struct hsmp_telemetry_table_rm_ccx {
+ __u64 core_c0[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u64 core_cc6[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u64 core_freq[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u64 core_freq_eff[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u64 core_temp[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+ __u64 core_power[HSMP_TELEMETRY_RM_CORES_PER_CCX];
+};
+
+/**
+ * struct hsmp_telemetry_table_rm - Top-level RM telemetry table.
+ * @iod: I/O die counters and per-rail/per-throttler/SYSTEM telemetry.
+ * @ccx: Per-CCX core counters, one entry per CCX.
+ *
+ * Layout of the table returned by HSMP_CLIENT_GET_METRICS_TABLE on the
+ * Family 1Ah client (Models 80h-8Fh and E0h-E3h), reported as table
+ * version 5 by HSMP_CLIENT_GET_METRICS_TABLE_VER. Units are
+ * firmware-defined; see the AMD HSMP/Ryzen Master SMC spec. Spare
+ * fields are firmware padding and must be preserved.
+ */
+struct hsmp_telemetry_table_rm {
+ struct hsmp_telemetry_table_rm_iod iod;
+ struct hsmp_telemetry_table_rm_ccx ccx[HSMP_TELEMETRY_RM_MAX_CCX];
+};
+
+#pragma pack(pop)
+
#endif /*_ASM_X86_AMD_HSMP_H_*/
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index f165d14cf61a..cc1e58fa664b 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -224,7 +224,7 @@ config ACPI_VIDEO
tristate "Video"
depends on BACKLIGHT_CLASS_DEVICE
depends on INPUT
- depends on ACPI_WMI || !X86
+ depends on ACPI_WMI
select THERMAL
help
This driver implements the ACPI Extensions For Display Adapters
diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c
index 326d4c6d132a..2050b8ff5384 100644
--- a/drivers/acpi/video_detect.c
+++ b/drivers/acpi/video_detect.c
@@ -77,8 +77,6 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv)
return AE_OK;
}
-/* This depends on ACPI_WMI which is X86 only */
-#ifdef CONFIG_X86
static bool nvidia_wmi_ec_supported(void)
{
struct wmi_brightness_args args = {
@@ -100,12 +98,6 @@ static bool nvidia_wmi_ec_supported(void)
*/
return args.ret == WMI_BRIGHTNESS_SOURCE_EC;
}
-#else
-static bool nvidia_wmi_ec_supported(void)
-{
- return false;
-}
-#endif
/* Force to use vendor driver when the ACPI device is known to be
* buggy */
diff --git a/drivers/gpu/drm/amd/amdgpu/Kconfig b/drivers/gpu/drm/amd/amdgpu/Kconfig
index 12e4a41bf1f0..50305630f533 100644
--- a/drivers/gpu/drm/amd/amdgpu/Kconfig
+++ b/drivers/gpu/drm/amd/amdgpu/Kconfig
@@ -30,9 +30,7 @@ config DRM_AMDGPU
# ACPI_VIDEO's dependencies must also be selected.
select INPUT if ACPI
select ACPI_VIDEO if ACPI
- # On x86 ACPI_VIDEO also needs ACPI_WMI
- select X86_PLATFORM_DEVICES if ACPI && X86
- select ACPI_WMI if ACPI && X86
+ select ACPI_WMI if ACPI
help
Choose this option if you have a recent AMD Radeon graphics card.
diff --git a/drivers/gpu/drm/gma500/Kconfig b/drivers/gpu/drm/gma500/Kconfig
index a2acaa699dd5..dc4c56638819 100644
--- a/drivers/gpu/drm/gma500/Kconfig
+++ b/drivers/gpu/drm/gma500/Kconfig
@@ -11,7 +11,6 @@ config DRM_GMA500
select ACPI_VIDEO if ACPI
select BACKLIGHT_CLASS_DEVICE if ACPI
select INPUT if ACPI
- select X86_PLATFORM_DEVICES if ACPI
select ACPI_WMI if ACPI
help
Say yes for an experimental 2D KMS framebuffer driver for the
diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig
index 5e939004b646..71426b9c91f0 100644
--- a/drivers/gpu/drm/i915/Kconfig
+++ b/drivers/gpu/drm/i915/Kconfig
@@ -28,7 +28,6 @@ config DRM_I915
# but for select to work, need to select ACPI_VIDEO's dependencies, ick
select BACKLIGHT_CLASS_DEVICE if ACPI
select INPUT if ACPI
- select X86_PLATFORM_DEVICES if ACPI
select ACPI_WMI if ACPI
select ACPI_VIDEO if ACPI
select SYNC_FILE
diff --git a/drivers/gpu/drm/radeon/Kconfig b/drivers/gpu/drm/radeon/Kconfig
index c479f0c0dd5c..3cd70aef770e 100644
--- a/drivers/gpu/drm/radeon/Kconfig
+++ b/drivers/gpu/drm/radeon/Kconfig
@@ -25,9 +25,7 @@ config DRM_RADEON
# ACPI_VIDEO's dependencies must also be selected.
select INPUT if ACPI
select ACPI_VIDEO if ACPI
- # On x86 ACPI_VIDEO also needs ACPI_WMI
- select X86_PLATFORM_DEVICES if ACPI && X86
- select ACPI_WMI if ACPI && X86
+ select ACPI_WMI if ACPI
help
Choose this option if you have an ATI Radeon graphics card. There
are both PCI and AGP versions. You don't need to choose this to
diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig
index 4d7dcaff2b91..502c7b3c1f16 100644
--- a/drivers/gpu/drm/xe/Kconfig
+++ b/drivers/gpu/drm/xe/Kconfig
@@ -4,7 +4,6 @@ config DRM_XE
depends on DRM && PCI
depends on KUNIT || !KUNIT
depends on INTEL_VSEC || !INTEL_VSEC
- depends on X86_PLATFORM_DEVICES || !(X86 && ACPI)
depends on PAGE_SIZE_4KB || COMPILE_TEST || BROKEN
select INTERVAL_TREE
# we need shmfs for the swappable backing store, and in particular
@@ -29,8 +28,8 @@ config DRM_XE
# but for select to work, need to select ACPI_VIDEO's dependencies, ick
select BACKLIGHT_CLASS_DEVICE if ACPI
select INPUT if ACPI
- select ACPI_VIDEO if X86 && ACPI
- select ACPI_WMI if X86 && ACPI
+ select ACPI_VIDEO if ACPI
+ select ACPI_WMI if ACPI
select SYNC_FILE
select CRC32
select SND_HDA_I915 if SND_HDA_CORE
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index e32e01b2a9bd..2a84775a13a9 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -33,6 +33,22 @@ config EC_ACER_ASPIRE1
laptop where this information is not properly exposed via the
standard ACPI devices.
+config EC_ASUS_GLYMUR
+ tristate "ASUS Zenbook A16 (Qualcomm) Embedded Controller driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on I2C
+ depends on HWMON
+ depends on INPUT
+ depends on PM
+ depends on THERMAL || THERMAL=n
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ Say M or Y here to enable the EC driver for the Qualcomm Snapdragon-
+ based ASUS Zenbook A16 (UX3607OA) and similar machines. It provides
+ temperature sensors readout, fan monitoring, keyboard and EC event
+ reporting.
+
config EC_HUAWEI_GAOKUN
tristate "Huawei Matebook E Go Embedded Controller driver"
depends on ARCH_QCOM || COMPILE_TEST
@@ -90,6 +106,30 @@ config EC_LENOVO_THINKPAD_T14S
Say M or Y here to include this support.
+config EC_LENOVO_YOGA_SLIM7X_GEN11
+ tristate "Lenovo Yoga Slim 7x Gen 11 Embedded Controller driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on I2C
+ depends on INPUT
+ depends on HWMON
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ Say Y here to enable the EC driver for the (Snapdragon X2 Elite,
+ glymur-based) Lenovo Yoga Slim 7x Gen 11 laptop. The EC provides
+ thermistors and CPU fan speed as hwmon channels, the keyboard
+ backlight as an LED, Fn-lock as a sysfs attribute, and the mic-mute
+ and airplane-mode keys.
+
+ This is the Gen 11 (glymur) machine specifically. Its EC is a Compal
+ design at i2c address 0x70 and speaks a different protocol from the
+ Qualcomm reference EC that the reference designs place at 0x76, so
+ EC_QCOM_HAMOA does not drive it - and conversely this driver does not
+ cover the X1E-based Yoga Slim 7x Gen 9.
+
+ To compile this driver as a module, choose M here: the module will
+ be called lenovo-yoga-slim7x-gen11-ec.
+
config EC_QCOM_HAMOA
tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
index 7681be4a46e9..3bc3d4669c27 100644
--- a/drivers/platform/arm64/Makefile
+++ b/drivers/platform/arm64/Makefile
@@ -6,7 +6,9 @@
#
obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
+obj-$(CONFIG_EC_ASUS_GLYMUR) += asus-glymur-ec.o
obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
+obj-$(CONFIG_EC_LENOVO_YOGA_SLIM7X_GEN11) += lenovo-yoga-slim7x-gen11-ec.o
obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o
diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c
index 08d0b155a197..501c62a096f8 100644
--- a/drivers/platform/arm64/acer-aspire1-ec.c
+++ b/drivers/platform/arm64/acer-aspire1-ec.c
@@ -514,7 +514,7 @@ static int aspire_ec_probe(struct i2c_client *client)
aspire_ec_irq_handler, IRQF_ONESHOT,
dev_name(dev), ec);
if (ret)
- return dev_err_probe(dev, ret, "Failed to request irq\n");
+ return ret;
return 0;
}
diff --git a/drivers/platform/arm64/asus-glymur-ec.c b/drivers/platform/arm64/asus-glymur-ec.c
new file mode 100644
index 000000000000..d75923fac9c4
--- /dev/null
+++ b/drivers/platform/arm64/asus-glymur-ec.c
@@ -0,0 +1,617 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * The EC firmware on the ASUS Zenbook A16 looks to be derived from the
+ * 'Qualcomm Hamoa EC' reference base with many major modifications. Some of
+ * the original interface is reused, albeit many of its core invariants (such
+ * as the capabilities bitmap) are absent.
+ *
+ * It presents itself as two I2C devices:
+ * [0x5b] is the custom ASUS interface
+ * [0x76] is the Hamoa EC-derived interface
+ */
+
+#include <asm/byteorder.h>
+#include <linux/cleanup.h>
+#include <linux/compiler.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/* Qualcomm-derived registers (main device @ 0x76) */
+#define ASUS_QCOM_EC_EVENT_CMD 0x05
+ #define ASUS_QCOM_EC_EVENT_NONE 0x00
+ #define ASUS_QCOM_EC_EVENT_INVALID 0xff
+
+#define ASUS_QCOM_EC_SUBCMD_FULL_VER 0x0f
+#define ASUS_QCOM_EC_FULL_VER_LEN 7
+
+/* This "read RAM" command isn't actually part of the Qualcomm standard */
+#define ASUS_QCOM_EC_RECM_OFFS 0x52
+
+#define ASUS_QCOM_EC_RAM_FAN_CPU 0x0602
+#define ASUS_QCOM_EC_RAM_AC_STATUS 0x0609
+#define ASUS_QCOM_EC_RAM_FAN_GPU 0x0624
+
+#define ASUS_QCOM_EC_MODERN_STANDBY_CMD 0x23
+#define ASUS_QCOM_EC_MODERN_STANDBY_ENTER 0x07
+#define ASUS_QCOM_EC_MODERN_STANDBY_EXIT 0x08
+
+/* ASUS-specific registers (secondary device @ 0x5b) */
+#define ASUS_EC_SUBDEV_ADDR 0x5b
+
+#define ASUS_EC_REQ_REG 0x10
+#define ASUS_EC_RESP_REG 0x11
+
+#define ASUS_EC_PAGE_MAILBOX 0xc4
+ #define ASUS_EC_MBOX_REG_CMD 0x30
+ #define ASUS_EC_MBOX_REG_SUBCMD 0x31
+ #define ASUS_EC_MBOX_REG_DATA 0x32
+
+#define ASUS_EC_MBOX_CMD_KBD 0x01
+ #define ASUS_EC_MBOX_SUBCMD_KBD_EN 0x81
+ #define ASUS_EC_MBOX_DATA_KBD_EN 0x04
+ #define ASUS_EC_MBOX_SUBCMD_KBD_LVL 0x87
+
+#define ASUS_EC_MBOX_CMD_MISC 0x02
+ #define ASUS_EC_MBOX_SUBCMD_MISC_ENABLE 0x83
+
+#define ASUS_EC_PAGE_TEMPERATURE 0xc6
+ #define ASUS_EC_TEMP_REG_CPU 0xa6
+ #define ASUS_EC_TEMP_REG_SOC 0x2a
+ #define ASUS_EC_TEMP_MAX 0x7f
+
+#define ASUS_EC_KBD_MAX_BRIGHTNESS 3
+
+static const u8 asus_ec_kbd_levels[] = {
+ [0] = 0x00,
+ [1] = 0x55,
+ [2] = 0xaa,
+ [3] = 0xff,
+};
+
+enum asus_ec_event {
+ ASUS_EC_EVT_CHARGE_STATE = 0x1c,
+ ASUS_EC_EVT_AC_STATE = 0x1d,
+ ASUS_EC_EVT_CRIT_TRIP = 0x1e,
+ ASUS_EC_EVT_THERMISTOR = 0x4f,
+ ASUS_EC_EVT_HOTKEY = 0xda,
+ ASUS_EC_EVT_FAN_STATUS = 0xdb,
+ ASUS_EC_EVT_THERMAL_TRIP = 0xe0,
+ ASUS_EC_EVT_CHARGE_COMMIT = 0xe4,
+};
+
+struct asus_ec {
+ struct i2c_client *client;
+ struct i2c_client *subdev_client;
+ /* Serializes all EC access across both I2C devices */
+ struct mutex lock;
+ struct device *hwmon_dev;
+ struct led_classdev kbd_led;
+};
+
+/* RAM addresses are sent MSB first, while returned data is LSB first. */
+struct asus_qcom_ec_ram_request {
+ __be16 addr;
+ u8 count;
+ u8 reserved[2];
+} __packed;
+
+struct asus_qcom_ec_ram_response {
+ __le16 data;
+ u8 unused[3];
+} __packed;
+
+static int asus_qcom_ec_write(struct asus_ec *ec, u8 offset,
+ const u8 *buf, u8 len)
+{
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_write_i2c_block_data(ec->client, offset, len, buf);
+ if (ret < 0) {
+ dev_dbg(&ec->client->dev,
+ "Main device write off %#x len %u failed: %d\n",
+ offset, len, ret);
+ }
+
+ return ret;
+}
+
+static int asus_qcom_ec_read(struct asus_ec *ec, u8 offset, u8 *buf, u8 len)
+{
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_read_i2c_block_data(ec->client, offset, len, buf);
+ if (ret < 0)
+ return ret;
+ if (ret < len) {
+ dev_err(&ec->client->dev,
+ "Short read off %#x: got %d of %u bytes\n",
+ offset, ret, len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int asus_qcom_ec_read_ram(struct asus_ec *ec, u16 addr, u8 count, u16 *out)
+{
+ struct asus_qcom_ec_ram_request req = {
+ .addr = cpu_to_be16(addr),
+ .count = count,
+ .reserved = {},
+ };
+ struct asus_qcom_ec_ram_response resp = { };
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ if (count != 1 && count != 2)
+ return -EINVAL;
+
+ ret = asus_qcom_ec_write(ec, ASUS_QCOM_EC_RECM_OFFS,
+ (const u8 *)&req, sizeof(req));
+ if (ret < 0)
+ return ret;
+
+ ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_RECM_OFFS,
+ (u8 *)&resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ if (count == 2)
+ *out = le16_to_cpu(resp.data);
+ else
+ *out = (u8)le16_to_cpu(resp.data);
+
+ return 0;
+}
+
+static int asus_ec_readb(struct asus_ec *ec, u8 page, u8 reg, u8 *out)
+{
+ u8 sel[2] = { page, reg };
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG,
+ sizeof(sel), sel);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(ec->subdev_client, ASUS_EC_RESP_REG);
+ if (ret < 0)
+ return ret;
+
+ *out = ret;
+
+ return 0;
+}
+
+static int asus_ec_writeb(struct asus_ec *ec, u8 page, u8 reg, u8 data)
+{
+ u8 sel[2] = { page, reg };
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG,
+ sizeof(sel), sel);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_write_byte_data(ec->subdev_client,
+ ASUS_EC_RESP_REG, data);
+}
+
+static int asus_ec_wait_mailbox(struct asus_ec *ec)
+{
+ int ret, tries;
+ u8 status;
+
+ lockdep_assert_held(&ec->lock);
+
+ for (tries = 5; tries > 0; tries--) {
+ ret = asus_ec_readb(ec, ASUS_EC_PAGE_MAILBOX,
+ ASUS_EC_MBOX_REG_CMD, &status);
+ if (ret < 0)
+ return ret;
+ if (!status)
+ return 0;
+ usleep_range(5000, 6000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int asus_ec_mailbox_cmd(struct asus_ec *ec, u8 cmd, u8 sub, u8 data)
+{
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = asus_ec_wait_mailbox(ec);
+ if (ret < 0)
+ return ret == -ETIMEDOUT ? -EBUSY : ret;
+
+ ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_SUBCMD, sub);
+ if (ret < 0)
+ return ret;
+
+ ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_DATA, data);
+ if (ret < 0)
+ return ret;
+
+ ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_CMD, cmd);
+ if (ret < 0)
+ return ret;
+
+ return asus_ec_wait_mailbox(ec);
+}
+
+static int asus_ec_enable_writes(struct asus_ec *ec)
+{
+ guard(mutex)(&ec->lock);
+
+ return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_MISC,
+ ASUS_EC_MBOX_SUBCMD_MISC_ENABLE, 1);
+}
+
+static int asus_ec_read_version(struct asus_ec *ec, u8 *major, u8 *minor, u8 *patch)
+{
+ u8 resp[ASUS_QCOM_EC_FULL_VER_LEN + 1];
+ int ret;
+
+ guard(mutex)(&ec->lock);
+
+ ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_SUBCMD_FULL_VER,
+ resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+ if (resp[0] < ASUS_QCOM_EC_FULL_VER_LEN)
+ return -EIO;
+
+ *major = resp[3];
+ *minor = resp[2];
+ *patch = resp[1];
+
+ return 0;
+}
+
+static int asus_ec_get_temp(struct asus_ec *ec, u8 reg, long *millideg)
+{
+ u8 raw;
+ int ret;
+
+ guard(mutex)(&ec->lock);
+
+ ret = asus_ec_readb(ec, ASUS_EC_PAGE_TEMPERATURE, reg, &raw);
+ if (ret < 0)
+ return ret;
+ if (raw > ASUS_EC_TEMP_MAX)
+ return -ENODATA;
+
+ *millideg = raw * MILLIDEGREE_PER_DEGREE;
+
+ return 0;
+}
+
+static int asus_ec_get_fan_rpm(struct asus_ec *ec, u16 base, long *rpm)
+{
+ u16 val;
+ int ret;
+
+ guard(mutex)(&ec->lock);
+
+ ret = asus_qcom_ec_read_ram(ec, base, 2, &val);
+ if (ret < 0)
+ return ret;
+
+ *rpm = val;
+
+ return 0;
+}
+
+static irqreturn_t asus_ec_irq(int irq, void *data)
+{
+ struct asus_ec *ec = data;
+ struct device *dev = &ec->client->dev;
+ int code;
+
+ scoped_guard(mutex, &ec->lock)
+ code = i2c_smbus_read_byte_data(ec->client, ASUS_QCOM_EC_EVENT_CMD);
+
+ if (code < 0) {
+ dev_err_ratelimited(dev, "Failed to read EC event: %d\n", code);
+ return IRQ_HANDLED;
+ }
+ if (code == ASUS_QCOM_EC_EVENT_NONE ||
+ code == ASUS_QCOM_EC_EVENT_INVALID)
+ return IRQ_HANDLED;
+
+ switch (code) {
+ case ASUS_EC_EVT_HOTKEY:
+ /* Present in DSDT, don't know what triggers it */
+ dev_dbg_ratelimited(dev, "EC hotkey event\n");
+ break;
+
+ case ASUS_EC_EVT_FAN_STATUS:
+ dev_dbg_ratelimited(dev, "Fan status changed\n");
+ break;
+
+ /* Maybe the temperature of the EC itself? */
+ case ASUS_EC_EVT_THERMAL_TRIP:
+ case ASUS_EC_EVT_CRIT_TRIP:
+ /* ASUS_EC_TEMP_SOC climbing above 91 *C or falling below 92 *C */
+ case ASUS_EC_EVT_THERMISTOR:
+ dev_dbg_ratelimited(dev, "Thermal event %#x\n", code);
+ break;
+
+ case ASUS_EC_EVT_AC_STATE: {
+ u16 ac = 0;
+ int ret;
+
+ /* TODO: ping SoCCP over the pmic_glink OEM-specific interface */
+ scoped_guard(mutex, &ec->lock)
+ ret = asus_qcom_ec_read_ram(ec, ASUS_QCOM_EC_RAM_AC_STATUS, 1, &ac);
+ if (ret < 0) {
+ dev_warn_ratelimited(dev,
+ "Failed to read AC status: %d\n",
+ ret);
+ break;
+ }
+ dev_dbg_ratelimited(dev, "AC event: 0x%x\n", ac);
+ break;
+ }
+ case ASUS_EC_EVT_CHARGE_STATE:
+ case ASUS_EC_EVT_CHARGE_COMMIT:
+ break;
+
+ default:
+ dev_info_ratelimited(dev, "unknown EC event: %#x\n", code);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int asus_ec_kbd_led_set(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct asus_ec *ec = container_of(led, struct asus_ec, kbd_led);
+ int ret;
+
+ if (brightness > ASUS_EC_KBD_MAX_BRIGHTNESS)
+ brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
+
+ guard(mutex)(&ec->lock);
+
+ ret = asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_EN,
+ ASUS_EC_MBOX_DATA_KBD_EN);
+ if (ret < 0)
+ return ret;
+
+ return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_LVL,
+ asus_ec_kbd_levels[brightness]);
+}
+
+enum asus_ec_temp_channel {
+ /* External sensor? */
+ ASUS_EC_TEMP_CPU,
+ /* Likely SoC-internal temp data communicated over a side channel */
+ ASUS_EC_TEMP_SOC,
+ ASUS_EC_TEMP_COUNT,
+};
+
+static umode_t asus_ec_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ case hwmon_temp:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static int asus_ec_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct asus_ec *ec = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ switch (channel) {
+ case ASUS_EC_TEMP_CPU:
+ return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_CPU, val);
+ case ASUS_EC_TEMP_SOC:
+ return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_SOC, val);
+ default:
+ return -EINVAL;
+ }
+ case hwmon_fan:
+ switch (channel) {
+ case 0:
+ return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_CPU, val);
+ case 1:
+ return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_GPU, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type != hwmon_temp || channel >= ASUS_EC_TEMP_COUNT)
+ return -EINVAL;
+
+ switch (channel) {
+ case ASUS_EC_TEMP_CPU:
+ *str = "CPU";
+ break;
+ case ASUS_EC_TEMP_SOC:
+ *str = "SoC";
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const asus_ec_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops asus_ec_hwmon_ops = {
+ .is_visible = asus_ec_hwmon_is_visible,
+ .read = asus_ec_hwmon_read,
+ .read_string = asus_ec_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info asus_ec_hwmon_chip_info = {
+ .ops = &asus_ec_hwmon_ops,
+ .info = asus_ec_hwmon_info,
+};
+
+static int asus_glymur_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ u8 major, minor, patch;
+ struct asus_ec *ec;
+ int ret;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->client = client;
+ i2c_set_clientdata(client, ec);
+
+ ret = devm_mutex_init(dev, &ec->lock);
+ if (ret)
+ return ret;
+
+ ec->subdev_client = devm_i2c_new_dummy_device(dev, client->adapter,
+ ASUS_EC_SUBDEV_ADDR);
+ if (IS_ERR(ec->subdev_client)) {
+ return dev_err_probe(dev, PTR_ERR(ec->subdev_client),
+ "Failed to register 0x5b EC device\n");
+ }
+
+ ret = asus_ec_enable_writes(ec);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable EC direct access: %d\n", ret);
+
+ ret = asus_ec_read_version(ec, &major, &minor, &patch);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read EC version: %d\n", ret);
+
+ dev_dbg(dev, "ASUS Zenbook EC version %u.%u.%u\n", major, minor, patch);
+
+ ec->kbd_led.name = "asus::kbd_backlight";
+ ec->kbd_led.max_brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
+ ec->kbd_led.brightness_set_blocking = asus_ec_kbd_led_set;
+ ec->kbd_led.flags = LED_CORE_SUSPENDRESUME;
+
+ ret = devm_led_classdev_register(dev, &ec->kbd_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register keyboard LED\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL, asus_ec_irq,
+ IRQF_ONESHOT, dev_name(dev), ec);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+ ec->hwmon_dev = devm_hwmon_device_register_with_info(dev,
+ "asus_glymur_ec",
+ ec,
+ &asus_ec_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(ec->hwmon_dev)) {
+ return dev_err_probe(dev, PTR_ERR(ec->hwmon_dev),
+ "Failed to register hwmon\n");
+ }
+
+ return 0;
+}
+
+static int asus_glymur_ec_modern_standby(struct asus_ec *ec, u8 val)
+{
+ guard(mutex)(&ec->lock);
+
+ return i2c_smbus_write_byte_data(ec->client,
+ ASUS_QCOM_EC_MODERN_STANDBY_CMD, val);
+}
+
+static int asus_glymur_ec_suspend(struct device *dev)
+{
+ return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
+ ASUS_QCOM_EC_MODERN_STANDBY_ENTER);
+}
+
+static int asus_glymur_ec_resume(struct device *dev)
+{
+ return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
+ ASUS_QCOM_EC_MODERN_STANDBY_EXIT);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(asus_glymur_ec_pm_ops,
+ asus_glymur_ec_suspend,
+ asus_glymur_ec_resume);
+
+static const struct of_device_id asus_glymur_ec_of_match[] = {
+ { .compatible = "asus,zenbook-a14-ux3407na-ec" },
+ { .compatible = "asus,zenbook-a16-ux3607oa-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, asus_glymur_ec_of_match);
+
+static const struct i2c_device_id asus_glymur_ec_id[] = {
+ { "asus-glymur-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, asus_glymur_ec_id);
+
+static struct i2c_driver asus_glymur_ec_driver = {
+ .driver = {
+ .name = "asus-glymur-ec",
+ .of_match_table = asus_glymur_ec_of_match,
+ .pm = pm_sleep_ptr(&asus_glymur_ec_pm_ops),
+ },
+ .probe = asus_glymur_ec_probe,
+ .id_table = asus_glymur_ec_id,
+};
+module_i2c_driver(asus_glymur_ec_driver);
+
+MODULE_DESCRIPTION("ASUS Zenbook A16 (Qualcomm) Embedded Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
index 80a8ba8b8dda..d4ce8ad6426c 100644
--- a/drivers/platform/arm64/huawei-gaokun-ec.c
+++ b/drivers/platform/arm64/huawei-gaokun-ec.c
@@ -783,7 +783,7 @@ static int gaokun_ec_probe(struct i2c_client *client)
gaokun_ec_irq_handler, IRQF_ONESHOT,
dev_name(dev), ec);
if (ret)
- return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+ return ret;
ec->hwmon_dev = devm_hwmon_device_register_with_info(dev, "gaokun_ec_hwmon",
ec, &gaokun_ec_hwmon_chip_info, NULL);
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
index e7acb66b77f2..7a875877c8f3 100644
--- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -488,8 +488,14 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
case T14S_EC_EVT_NONE:
break;
case T14S_EC_EVT_KEY_FN_SPACE:
+ /*
+ * Firmware already cycles the keyboard backlight
+ * (off / low / high). Notify userspace through the LED
+ * class only. Emitting KEY_KBDILLUMTOGGLE as well makes
+ * desktop environments toggle the LED a second time.
+ */
t14s_kbd_bl_update(ec);
- fallthrough;
+ break;
case T14S_EC_EVT_KEY_FN_F4:
case T14S_EC_EVT_KEY_FN_F7:
case T14S_EC_EVT_KEY_FN_4:
@@ -594,7 +600,7 @@ static int t14s_ec_probe(struct i2c_client *client)
t14s_ec_irq_handler,
IRQF_ONESHOT, dev_name(dev), ec);
if (ret < 0)
- return dev_err_probe(dev, ret, "Failed to get IRQ\n");
+ return ret;
/*
* Disable wakeup support by default, because the driver currently does
diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c
index a8600a977fbc..cad40c58c55b 100644
--- a/drivers/platform/arm64/lenovo-yoga-c630.c
+++ b/drivers/platform/arm64/lenovo-yoga-c630.c
@@ -221,7 +221,7 @@ static int yoga_c630_ec_probe(struct i2c_client *client)
NULL, yoga_c630_ec_thread_intr,
IRQF_ONESHOT, "yoga_c630_ec", ec);
if (ret < 0)
- return dev_err_probe(dev, ret, "unable to request irq\n");
+ return ret;
ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec);
if (ret)
diff --git a/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
new file mode 100644
index 000000000000..257d64eb0e56
--- /dev/null
+++ b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
@@ -0,0 +1,530 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kstrtox.h>
+#include <linux/leds.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define SLIM7X11_EC_BLOCK_READ 0x01
+#define SLIM7X11_EC_BLOCK_WRITE 0x02
+#define SLIM7X11_EC_RESP_LEN 6
+#define SLIM7X11_EC_REQ_LEN 7
+
+#define SLIM7X11_EC_CMD_RAM_READ 0xb0
+#define SLIM7X11_EC_CMD_RAM_WRITE 0xb1
+#define SLIM7X11_EC_CMD_FAN 0x46
+#define SLIM7X11_EC_FAN_GET_SPEED 0x81
+
+#define SLIM7X11_EC_CMD_EVENT 0x84
+#define SLIM7X11_EC_EVT_MAX_DRAIN 64
+#define SLIM7X11_EC_EVT_FN_LOCK 0x0f
+#define SLIM7X11_EC_EVT_MIC_MUTE 0x18
+#define SLIM7X11_EC_EVT_FLIGHT_MODE 0x19
+
+#define SLIM7X11_EC_SCUK 0x1c
+#define SLIM7X11_EC_SCUK_MEDIA BIT(7)
+#define SLIM7X11_EC_SCUK_LED_OFF BIT(5)
+#define SLIM7X11_EC_SCUK_MEDIA_MASK (SLIM7X11_EC_SCUK_MEDIA | SLIM7X11_EC_SCUK_LED_OFF)
+
+#define SLIM7X11_EC_KBLT 0x03
+#define SLIM7X11_EC_KBLT_LEVEL GENMASK(1, 0)
+#define SLIM7X11_EC_KBLT_AUTO 3
+#define SLIM7X11_EC_KBLT_MAX 2
+
+#define SLIM7X11_EC_TEMP_FIRST 0x12
+#define SLIM7X11_EC_NUM_TEMP 6
+#define SLIM7X11_EC_TEMP_MAX 120
+
+struct slim7x11_ec {
+ struct i2c_client *client;
+ /* serializes all EC access */
+ struct mutex lock;
+ unsigned long temp_present;
+ struct led_classdev kbd_led;
+ u8 kblt_level;
+ bool kblt_restore;
+ struct input_dev *keys;
+};
+
+static int slim7x11_ec_command(struct slim7x11_ec *ec, u8 cmd, u8 arg0, u8 arg1,
+ u8 resp[SLIM7X11_EC_RESP_LEN])
+{
+ u8 req[SLIM7X11_EC_REQ_LEN] = { cmd, arg0, arg1 };
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_write_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_WRITE, sizeof(req), req);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(2000, 3000);
+
+ ret = i2c_smbus_read_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_READ,
+ SLIM7X11_EC_RESP_LEN, resp);
+ if (ret < 0)
+ return ret;
+ if (ret != SLIM7X11_EC_RESP_LEN)
+ return -EIO;
+
+ return 0;
+}
+
+static int slim7x11_ec_ram_read(struct slim7x11_ec *ec, u8 addr, u8 *val)
+{
+ u8 resp[SLIM7X11_EC_RESP_LEN];
+ int ret;
+
+ ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_READ, addr, 0, resp);
+ if (ret)
+ return ret;
+
+ *val = resp[0];
+ return 0;
+}
+
+static int slim7x11_ec_ram_write(struct slim7x11_ec *ec, u8 addr, u8 val)
+{
+ u8 resp[SLIM7X11_EC_RESP_LEN];
+
+ return slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_WRITE, addr, val, resp);
+}
+
+static int slim7x11_ec_fan_read(struct slim7x11_ec *ec, long *rpm)
+{
+ u8 resp[SLIM7X11_EC_RESP_LEN];
+ int ret;
+
+ ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_FAN, SLIM7X11_EC_FAN_GET_SPEED, 0, resp);
+ if (ret)
+ return ret;
+
+ *rpm = resp[0] * 100;
+ return 0;
+}
+
+static umode_t slim7x11_ec_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct slim7x11_ec *ec = drvdata;
+
+ switch (type) {
+ case hwmon_temp:
+ if (!(ec->temp_present & BIT(channel)))
+ return 0;
+ return 0444;
+ case hwmon_fan:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static int slim7x11_ec_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct slim7x11_ec *ec = dev_get_drvdata(dev);
+ int ret;
+ u8 raw;
+
+ guard(mutex)(&ec->lock);
+
+ switch (type) {
+ case hwmon_temp:
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + channel, &raw);
+ if (ret)
+ return ret;
+ if (!raw)
+ return -ENODATA;
+ if (raw > SLIM7X11_EC_TEMP_MAX)
+ return -EIO;
+ *val = raw * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ case hwmon_fan:
+ return slim7x11_ec_fan_read(ec, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const char * const slim7x11_ec_temp_labels[SLIM7X11_EC_NUM_TEMP] = {
+ "Charger A",
+ "CPU VR",
+ "5V rail",
+ "Ambient",
+ "Charger B",
+ "Thermistor 6",
+};
+
+static int slim7x11_ec_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = slim7x11_ec_temp_labels[channel];
+ return 0;
+ case hwmon_fan:
+ *str = "CPU fan";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static enum led_brightness slim7x11_ec_kbd_led_get(struct led_classdev *led)
+{
+ struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led);
+ u8 raw;
+
+ guard(mutex)(&ec->lock);
+
+ if (slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw))
+ return 0;
+
+ raw = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw);
+
+ if (raw == SLIM7X11_EC_KBLT_AUTO)
+ return SLIM7X11_EC_KBLT_MAX;
+
+ return raw;
+}
+
+static int slim7x11_ec_kbd_led_level(struct slim7x11_ec *ec, u8 level, u8 *old)
+{
+ u8 raw;
+ int ret;
+
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw);
+ if (ret)
+ return ret;
+
+ if (old)
+ *old = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw);
+
+ FIELD_MODIFY(SLIM7X11_EC_KBLT_LEVEL, &raw, level);
+
+ return slim7x11_ec_ram_write(ec, SLIM7X11_EC_KBLT, raw);
+}
+
+static int slim7x11_ec_kbd_led_set(struct led_classdev *led, enum led_brightness brightness)
+{
+ struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led);
+
+ guard(mutex)(&ec->lock);
+
+ return slim7x11_ec_kbd_led_level(ec, brightness, NULL);
+}
+
+static const struct hwmon_ops slim7x11_ec_hwmon_ops = {
+ .is_visible = slim7x11_ec_is_visible,
+ .read = slim7x11_ec_read,
+ .read_string = slim7x11_ec_read_string,
+};
+
+static const struct hwmon_channel_info * const slim7x11_ec_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ NULL
+};
+
+static const struct hwmon_chip_info slim7x11_ec_chip_info = {
+ .ops = &slim7x11_ec_hwmon_ops,
+ .info = slim7x11_ec_hwmon_info,
+};
+
+static int slim7x11_ec_fn_lock_toggle(struct slim7x11_ec *ec)
+{
+ u8 raw;
+ int ret;
+
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
+ if (ret)
+ return ret;
+
+ if (raw & SLIM7X11_EC_SCUK_MEDIA)
+ raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK;
+ else
+ raw |= SLIM7X11_EC_SCUK_MEDIA_MASK;
+
+ return slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw);
+}
+
+static void slim7x11_ec_report_key(struct slim7x11_ec *ec, unsigned int code)
+{
+ if (!ec->keys)
+ return;
+
+ input_report_key(ec->keys, code, 1);
+ input_sync(ec->keys);
+ input_report_key(ec->keys, code, 0);
+ input_sync(ec->keys);
+}
+
+static irqreturn_t slim7x11_ec_irq(int irq, void *data)
+{
+ struct slim7x11_ec *ec = data;
+ unsigned int i;
+
+ guard(mutex)(&ec->lock);
+
+ for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) {
+ u8 resp[SLIM7X11_EC_RESP_LEN];
+
+ if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp))
+ break;
+ if (!resp[0])
+ break;
+
+ switch (resp[0]) {
+ case SLIM7X11_EC_EVT_FN_LOCK:
+ if (!slim7x11_ec_fn_lock_toggle(ec))
+ sysfs_notify(&ec->client->dev.kobj, NULL, "fn_lock");
+ break;
+ case SLIM7X11_EC_EVT_MIC_MUTE:
+ slim7x11_ec_report_key(ec, KEY_MICMUTE);
+ break;
+ case SLIM7X11_EC_EVT_FLIGHT_MODE:
+ slim7x11_ec_report_key(ec, KEY_RFKILL);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void slim7x11_ec_register_keys(struct slim7x11_ec *ec)
+{
+ struct device *dev = &ec->client->dev;
+ struct input_dev *input;
+ int ret;
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_warn(dev, "no memory for the hotkey device\n");
+ return;
+ }
+
+ input->name = "Lenovo Yoga Slim 7x hotkeys";
+ input->phys = "lenovo-yoga-slim7x-gen11-ec/input0";
+ input->id.bustype = BUS_I2C;
+ input_set_capability(input, EV_KEY, KEY_MICMUTE);
+ input_set_capability(input, EV_KEY, KEY_RFKILL);
+
+ ret = input_register_device(input);
+ if (ret) {
+ dev_warn(dev, "cannot register the hotkey device: %d\n", ret);
+ return;
+ }
+
+ ec->keys = input;
+}
+
+static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct slim7x11_ec *ec = dev_get_drvdata(dev);
+ u8 raw;
+ int ret;
+
+ scoped_guard(mutex, &ec->lock) {
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
+ if (ret)
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%u\n", !(raw & SLIM7X11_EC_SCUK_MEDIA));
+}
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct slim7x11_ec *ec = dev_get_drvdata(dev);
+ bool enable;
+ u8 raw;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&ec->lock);
+
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
+ if (ret)
+ return ret;
+
+ if (enable)
+ raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK;
+ else
+ raw |= SLIM7X11_EC_SCUK_MEDIA_MASK;
+
+ ret = slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(fn_lock);
+
+static struct attribute *slim7x11_ec_attrs[] = {
+ &dev_attr_fn_lock.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(slim7x11_ec);
+
+static int slim7x11_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon;
+ struct slim7x11_ec *ec;
+ unsigned int i;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ return dev_err_probe(dev, -ENODEV, "adapter lacks SMBus block transfers\n");
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->client = client;
+ i2c_set_clientdata(client, ec);
+
+ ret = devm_mutex_init(dev, &ec->lock);
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &ec->lock) {
+ for (i = 0; i < SLIM7X11_EC_NUM_TEMP; i++) {
+ u8 raw;
+
+ ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + i, &raw);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read thermistor %u\n", i);
+
+ if (raw > 0 && raw <= SLIM7X11_EC_TEMP_MAX)
+ ec->temp_present |= BIT(i);
+ }
+ }
+
+ if (!ec->temp_present)
+ return dev_err_probe(dev, -ENODEV, "no thermistor reported a usable value\n");
+
+ dev_dbg(dev, "thermistors present: %#lx\n", ec->temp_present);
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "yoga_slim7x_ec", ec,
+ &slim7x11_ec_chip_info, NULL);
+ if (IS_ERR(hwmon))
+ return PTR_ERR(hwmon);
+
+ ec->kbd_led.name = "platform::kbd_backlight";
+ ec->kbd_led.max_brightness = SLIM7X11_EC_KBLT_MAX;
+ ec->kbd_led.brightness_get = slim7x11_ec_kbd_led_get;
+ ec->kbd_led.brightness_set_blocking = slim7x11_ec_kbd_led_set;
+
+ ret = devm_led_classdev_register(dev, &ec->kbd_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register keyboard backlight\n");
+
+ if (client->irq) {
+ slim7x11_ec_register_keys(ec);
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL, slim7x11_ec_irq,
+ IRQF_ONESHOT, "lenovo-yoga-slim7x-gen11-ec", ec);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot request the EC interrupt\n");
+
+ scoped_guard(mutex, &ec->lock) {
+ for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) {
+ u8 resp[SLIM7X11_EC_RESP_LEN];
+
+ if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp))
+ break;
+ if (!resp[0])
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int slim7x11_ec_suspend(struct device *dev)
+{
+ struct slim7x11_ec *ec = dev_get_drvdata(dev);
+
+ guard(mutex)(&ec->lock);
+ ec->kblt_restore = !slim7x11_ec_kbd_led_level(ec, 0, &ec->kblt_level);
+
+ return 0;
+}
+
+static int slim7x11_ec_resume(struct device *dev)
+{
+ struct slim7x11_ec *ec = dev_get_drvdata(dev);
+
+ guard(mutex)(&ec->lock);
+ if (ec->kblt_restore) {
+ ec->kblt_restore = false;
+ slim7x11_ec_kbd_led_level(ec, ec->kblt_level, NULL);
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(slim7x11_ec_pm_ops, slim7x11_ec_suspend, slim7x11_ec_resume);
+
+static const struct of_device_id slim7x11_ec_of_match[] = {
+ { .compatible = "lenovo,yoga-slim7x-gen11-ec" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, slim7x11_ec_of_match);
+
+static const struct i2c_device_id slim7x11_ec_i2c_id[] = {
+ { .name = "slim7x-gen11-ec" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, slim7x11_ec_i2c_id);
+
+static struct i2c_driver slim7x11_ec_driver = {
+ .driver = {
+ .name = "lenovo-yoga-slim7x-gen11-ec",
+ .of_match_table = slim7x11_ec_of_match,
+ .dev_groups = slim7x11_ec_groups,
+ .pm = pm_sleep_ptr(&slim7x11_ec_pm_ops),
+ },
+ .probe = slim7x11_ec_probe,
+ .id_table = slim7x11_ec_i2c_id,
+};
+module_i2c_driver(slim7x11_ec_driver);
+
+MODULE_AUTHOR("Oleg Keri <okerixx@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Slim 7x Gen 11 embedded controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c
index 4d2ad042a7f8..d34afd901324 100644
--- a/drivers/platform/arm64/qcom-hamoa-ec.c
+++ b/drivers/platform/arm64/qcom-hamoa-ec.c
@@ -331,17 +331,28 @@ static const struct thermal_cooling_device_ops qcom_ec_thermal_ops = {
static int qcom_ec_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
+ EC_MODERN_STANDBY_EXIT);
+ enable_irq(client->irq);
- return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
- EC_MODERN_STANDBY_EXIT);
+ return ret;
}
static int qcom_ec_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ /* Keep SCI reads from racing the I2C controller's system sleep. */
+ disable_irq(client->irq);
+ ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
+ EC_MODERN_STANDBY_ENTER);
+ if (ret)
+ enable_irq(client->irq);
- return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
- EC_MODERN_STANDBY_ENTER);
+ return ret;
}
static int qcom_ec_probe(struct i2c_client *client)
diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig
index 447528797d07..db7833842142 100644
--- a/drivers/platform/loongarch/Kconfig
+++ b/drivers/platform/loongarch/Kconfig
@@ -23,6 +23,7 @@ config LOONGSON_LAPTOP
depends on INPUT
depends on MACH_LOONGSON64
select ACPI_VIDEO
+ select ACPI_WMI
select INPUT_SPARSEKMAP
default y
help
diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c
index 664bec20a7ae..d67215df7371 100644
--- a/drivers/platform/mellanox/mlxbf-tmfifo.c
+++ b/drivers/platform/mellanox/mlxbf-tmfifo.c
@@ -1405,7 +1405,6 @@ static int mlxbf_tmfifo_probe(struct platform_device *pdev)
mlxbf_tmfifo_irq_handler, 0,
"tmfifo", &fifo->irq_info[i]);
if (rc) {
- dev_err(dev, "devm_request_irq failed\n");
fifo->irq_info[i].irq = 0;
return rc;
}
diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c
index d246772aafd6..d6549dd21772 100644
--- a/drivers/platform/mellanox/mlxreg-hotplug.c
+++ b/drivers/platform/mellanox/mlxreg-hotplug.c
@@ -740,10 +740,8 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)
err = devm_request_irq(&pdev->dev, priv->irq,
mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING
| IRQF_SHARED, "mlxreg-hotplug", priv);
- if (err) {
- dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
+ if (err)
return err;
- }
disable_irq(priv->irq);
spin_lock_init(&priv->lock);
diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c
index 4a2d36f7331e..063f7e55c421 100644
--- a/drivers/platform/olpc/olpc-ec.c
+++ b/drivers/platform/olpc/olpc-ec.c
@@ -269,7 +269,7 @@ static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
mutex_lock(&ec_dbgfs_lock);
- size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
+ size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf) - 1, ppos, buf, size);
m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
&ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2],
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index d37a7e4c6b02..cf3ae252b1c4 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -410,13 +410,14 @@ static const struct software_node *ssam_node_group_sp11[] = {
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
+ &ssam_node_tmp_perf_profile_with_fan,
&ssam_node_tmp_sensors,
+ &ssam_node_fan_speed,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
&ssam_node_hid_kip_touchpad,
&ssam_node_hid_kip_fwupd,
- &ssam_node_hid_sam_sensors,
- &ssam_node_kip_tablet_switch,
+ &ssam_node_pos_tablet_switch,
NULL,
};
@@ -516,6 +517,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
+ /* Surface Laptop 8th Edition (13.8") */
+ { .compatible = "microsoft,surface-laptop-8-13in", (void *)ssam_node_group_sl7 },
{ },
};
diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig
index d62f51ff3b7f..11c221a58e04 100644
--- a/drivers/platform/wmi/Kconfig
+++ b/drivers/platform/wmi/Kconfig
@@ -5,7 +5,7 @@
menuconfig ACPI_WMI
tristate "ACPI-WMI support"
- depends on ACPI && X86
+ depends on ACPI
select NLS
help
This option enables support for the ACPI-WMI driver core.
@@ -34,4 +34,16 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
source "drivers/platform/wmi/tests/Kconfig"
+config WMI_BMOF
+ tristate "WMI embedded Binary MOF driver"
+ default ACPI_WMI
+ help
+ Say Y here if you want to be able to read the firmware-embedded
+ WMI Binary MOF (Managed Object Format) data. Using this requires
+ userspace tools, see Documentation/wmi/devices/wmi-bmof.rst for
+ details.
+
+ To compile this driver as a module, choose M here: the module will
+ be called wmi-bmof.
+
endif # ACPI_WMI
diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile
index 2feff94a5594..ee2f27ea20d1 100644
--- a/drivers/platform/wmi/Makefile
+++ b/drivers/platform/wmi/Makefile
@@ -9,3 +9,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o
# Unit tests
obj-y += tests/
+
+# Binary MOF driver
+wmi-bmof-y := bmof.o
+obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/wmi/bmof.c
index 6623cf60e4b4..6623cf60e4b4 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/wmi/bmof.c
diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c
index 529825dcfbfe..a99da9b7039d 100644
--- a/drivers/platform/wmi/core.c
+++ b/drivers/platform/wmi/core.c
@@ -145,7 +145,7 @@ static inline void get_acpi_method_name(const struct wmi_block *wblock,
buffer[4] = '\0';
}
-static int wmidev_match_guid(struct device *dev, const void *data)
+static int wmidev_match_guid_legacy(struct device *dev, const void *data)
{
struct wmi_block *wblock = dev_to_wblock(dev);
const guid_t *guid = data;
@@ -218,7 +218,7 @@ static struct wmi_device *wmi_find_device_by_guid(const char *guid_string)
if (ret < 0)
return ERR_PTR(ret);
- dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid);
+ dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid_legacy);
if (!dev)
return ERR_PTR(-ENODEV);
@@ -800,10 +800,10 @@ acpi_status wmi_remove_notify_handler(const char *guid)
EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
/**
- * wmi_has_guid - Check if a GUID is available
+ * wmi_has_guid - Check if a GUID is available (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
*
- * Check if a given GUID is defined by _WDG.
+ * Check if a given GUID is available and usable with the legacy GUID-based API.
*
* Return: True if GUID is available, false otherwise.
*/
@@ -821,6 +821,31 @@ bool wmi_has_guid(const char *guid_string)
}
EXPORT_SYMBOL_GPL(wmi_has_guid);
+static int wmidev_match_guid(struct device *dev, void *data)
+{
+ struct wmi_block *wblock = dev_to_wblock(dev);
+ const guid_t *guid = data;
+
+ if (guid_equal(guid, &wblock->gblock.guid))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * wmidev_exists - Check if a WMI device with a given GUID exists
+ * @guid: GUID of the WMI device to search for
+ *
+ * Check if a WMI device with a given GUID exists at the given moment.
+ *
+ * Return: True if a matching WMI device exists, false otherwise.
+ */
+bool wmidev_exists(const guid_t *guid)
+{
+ return bus_for_each_dev(&wmi_bus_type, NULL, (void *)guid, wmidev_match_guid) == 1;
+}
+EXPORT_SYMBOL_GPL(wmidev_exists);
+
/**
* wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 4851bf45157d..9ce384e0475e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -16,18 +16,6 @@ menuconfig X86_PLATFORM_DEVICES
if X86_PLATFORM_DEVICES
-config WMI_BMOF
- tristate "WMI embedded Binary MOF driver"
- depends on ACPI_WMI
- default ACPI_WMI
- help
- Say Y here if you want to be able to read a firmware-embedded
- WMI Binary MOF (Managed Object Format) data. Using this requires
- userspace tools and may be rather tedious.
-
- To compile this driver as a module, choose M here: the module will
- be called wmi-bmof.
-
config HUAWEI_WMI
tristate "Huawei WMI laptop extras driver"
depends on ACPI_BATTERY
@@ -189,6 +177,18 @@ config ACER_WMI
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
here.
+config ACER_WMI_BATTERY
+ tristate "Acer WMI Battery"
+ depends on ACPI_WMI
+ depends on ACPI_BATTERY
+ depends on DMI
+ help
+ This is a driver for Acer laptops with battery health control. It
+ adds charge limit control and battery temperature reporting.
+
+ If you have an ACPI-WMI Battery compatible Acer laptop, say Y or M
+ here.
+
source "drivers/platform/x86/amd/Kconfig"
config ADV_SWBUTTON
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index cab20a4145e3..229057234d00 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -4,9 +4,6 @@
# x86 Platform-Specific Drivers
#
-# Windows Management Interface
-obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
-
# WMI drivers
obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
@@ -20,6 +17,7 @@ obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o
obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
+obj-$(CONFIG_ACER_WMI_BATTERY) += acer-wmi-battery.o
# AMD
obj-y += amd/
diff --git a/drivers/platform/x86/acer-wmi-battery.c b/drivers/platform/x86/acer-wmi-battery.c
new file mode 100644
index 000000000000..10a34d4ab058
--- /dev/null
+++ b/drivers/platform/x86/acer-wmi-battery.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * acer-wmi-battery.c: Acer battery health control driver
+ *
+ * This is a driver for the WMI battery health control interface found
+ * on some Acer laptops. This interface allows to enable/disable a
+ * battery charge limit ("health mode") and exposes the battery temperature.
+ *
+ * Based on acer-wmi-battery https://github.com/frederik-h/acer-wmi-battery/
+ *
+ * Copyright (C) 2022-2025 Frederik Harwath <frederik@harwath.name>
+ */
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/byteorder/generic.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <linux/wmi.h>
+
+#include <acpi/battery.h>
+
+#define DRIVER_NAME "acer-wmi-battery"
+
+#define ACER_BATTERY_GUID "79772EC5-04B1-4BFD-843C-61E7F77B6CC9"
+
+/*
+ * The Acer OEM software seems to always use this battery index,
+ * so we emulate this behaviour to not confuse the underlying firmware.
+ *
+ * However this also means that we only fully support devices with a
+ * single battery for now.
+ */
+#define ACER_BATTERY_INDEX 0x1
+
+static bool force;
+module_param_unsafe(force, bool, 0);
+MODULE_PARM_DESC(force, "Enable health mode control without checking for supported devices\n");
+
+struct get_battery_health_control_status_input {
+ u8 battery_no;
+ u8 function_query;
+ u8 reserved[2];
+} __packed;
+
+struct get_battery_health_control_status_output {
+ u8 function_list;
+ u8 ret[2];
+ u8 function_status[5];
+} __packed;
+
+struct set_battery_health_control_input {
+ u8 battery_no;
+ u8 function_mask;
+ u8 function_status;
+ u8 reserved_in[5];
+} __packed;
+
+struct set_battery_health_control_output {
+ __le16 ret;
+ __le16 reserved_out;
+} __packed;
+
+enum battery_mode {
+ HEALTH_MODE = 1,
+ CALIBRATION_MODE = 2,
+};
+
+struct acer_wmi_battery_data {
+ struct acpi_battery_hook hook;
+ struct wmi_device *wdev;
+ const struct power_supply_ext *battery_ext;
+};
+
+static bool health_mode __ro_after_init;
+
+static int acer_wmi_battery_get_information(struct acer_wmi_battery_data *data,
+ u32 index, u32 battery, u32 *result)
+{
+ u32 args[2] = { index, battery };
+ struct wmi_buffer input = { .length = sizeof(args), .data = args };
+ struct wmi_buffer output;
+ int ret;
+
+ ret = wmidev_invoke_method(data->wdev, 0, 19, &input, &output, sizeof(*result));
+ if (ret)
+ return ret;
+
+ *result = le32_to_cpup((__le32 *)output.data);
+
+ kfree(output.data);
+
+ return 0;
+}
+
+static int acer_wmi_battery_get_health_control_status(struct acer_wmi_battery_data *data,
+ bool *health_mode)
+{
+ /*
+ * Acer Care Center seems to always call the WMI method
+ * with fixed parameters. This yields information about
+ * the availability and state of both health and
+ * calibration mode. The modes probably apply to
+ * all batteries of the system.
+ */
+ struct get_battery_health_control_status_input args = {
+ .battery_no = ACER_BATTERY_INDEX,
+ .function_query = 0x1,
+ .reserved = { 0x0, 0x0 },
+ };
+ struct wmi_buffer input = { .length = sizeof(args), .data = &args };
+ struct wmi_buffer output;
+ int ret;
+
+ ret = wmidev_invoke_method(data->wdev, 0, 20, &input, &output,
+ sizeof(struct get_battery_health_control_status_output));
+ if (ret)
+ return ret;
+
+ struct get_battery_health_control_status_output *status_output __free(kfree) = output.data;
+
+ if (!(status_output->function_list & HEALTH_MODE))
+ return -EINVAL;
+
+ *health_mode = status_output->function_status[0] > 0;
+
+ return 0;
+}
+
+static int acer_wmi_battery_set_health_control(struct acer_wmi_battery_data *data,
+ u8 function, bool function_status)
+{
+ struct set_battery_health_control_input args = {
+ .battery_no = ACER_BATTERY_INDEX,
+ .function_mask = function,
+ .function_status = function_status ? 1 : 0,
+ .reserved_in = { 0x0, 0x0, 0x0, 0x0, 0x0 },
+ };
+ struct wmi_buffer input = { .length = sizeof(args), .data = &args };
+ struct wmi_buffer output;
+ int ret;
+
+ ret = wmidev_invoke_method(data->wdev, 0, 21, &input, &output,
+ sizeof(struct set_battery_health_control_output));
+ if (ret)
+ return ret;
+
+ struct set_battery_health_control_output *status_output __free(kfree) = output.data;
+ if (le16_to_cpu(status_output->ret) != 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int acer_battery_ext_property_get(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct acer_wmi_battery_data *data = ext_data;
+ bool health_mode;
+ u32 value;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ ret = acer_wmi_battery_get_health_control_status(data, &health_mode);
+ if (ret)
+ return ret;
+
+ val->intval = health_mode ? POWER_SUPPLY_CHARGE_TYPE_LONGLIFE
+ : POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = acer_wmi_battery_get_information(data, 0x8, ACER_BATTERY_INDEX, &value);
+ if (ret)
+ return ret;
+
+ if (value > U16_MAX)
+ return -ERANGE;
+
+ val->intval = value + ABSOLUTE_ZERO_MILLICELSIUS / MILLIDEGREE_PER_DECIDEGREE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int acer_battery_ext_property_set(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct acer_wmi_battery_data *data = ext_data;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ return acer_wmi_battery_set_health_control(data, HEALTH_MODE,
+ val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int acer_battery_ext_property_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const enum power_supply_property acer_battery_properties_v1[] = {
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static const enum power_supply_property acer_battery_properties_v2[] = {
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+static const struct power_supply_ext acer_wmi_battery_extension_v1 = {
+ .name = DRIVER_NAME,
+ .properties = acer_battery_properties_v1,
+ .num_properties = ARRAY_SIZE(acer_battery_properties_v1),
+ .get_property = acer_battery_ext_property_get,
+};
+
+static const struct power_supply_ext acer_wmi_battery_extension_v2 = {
+ .name = DRIVER_NAME,
+ .properties = acer_battery_properties_v2,
+ .num_properties = ARRAY_SIZE(acer_battery_properties_v2),
+ .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE),
+ .get_property = acer_battery_ext_property_get,
+ .set_property = acer_battery_ext_property_set,
+ .property_is_writeable = acer_battery_ext_property_is_writeable,
+};
+
+static int acer_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook);
+
+ return power_supply_register_extension(battery, data->battery_ext,
+ &data->wdev->dev, data);
+}
+
+static int acer_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook);
+
+ power_supply_unregister_extension(battery, data->battery_ext);
+
+ return 0;
+}
+
+static int acer_wmi_battery_probe(struct wmi_device *wdev, const void *context)
+{
+ struct acer_wmi_battery_data *data;
+
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&wdev->dev, data);
+ data->wdev = wdev;
+ data->battery_ext = health_mode ? &acer_wmi_battery_extension_v2
+ : &acer_wmi_battery_extension_v1;
+ data->hook.name = "Acer Battery Extension";
+ data->hook.add_battery = acer_battery_add;
+ data->hook.remove_battery = acer_battery_remove;
+
+ return devm_battery_hook_register(&data->wdev->dev, &data->hook);
+}
+
+static const struct wmi_device_id acer_wmi_battery_id_table[] = {
+ { ACER_BATTERY_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, acer_wmi_battery_id_table);
+
+static struct wmi_driver acer_wmi_battery_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = acer_wmi_battery_id_table,
+ .probe = acer_wmi_battery_probe,
+ .no_singleton = true,
+};
+
+static const struct dmi_system_id acer_wmi_battery_health_mode_table[] __initconst = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-510P"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-24PT"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-44P"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A715-42G"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-57"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Predator PHN16-71"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-34"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-43"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFE16-44"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFG16-72"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX14-71G"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX16-61G"),
+ },
+ },
+ {}
+};
+
+static int __init acer_wmi_battery_module_init(void)
+{
+ if (dmi_check_system(acer_wmi_battery_health_mode_table) || force)
+ health_mode = true;
+ else
+ health_mode = false;
+
+ return wmi_driver_register(&acer_wmi_battery_driver);
+}
+
+static void __exit acer_wmi_battery_module_exit(void)
+{
+ wmi_driver_unregister(&acer_wmi_battery_driver);
+}
+
+module_init(acer_wmi_battery_module_init);
+module_exit(acer_wmi_battery_module_exit);
+
+MODULE_AUTHOR("Frederik Harwath <frederik@harwath.name>");
+MODULE_AUTHOR("Jelle van der Waa <jelle@vdwaa.nl>");
+MODULE_DESCRIPTION("Acer battery health control WMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index 61ae622c93d9..3a018fdf5138 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -400,11 +400,12 @@ struct quirk_entry {
u8 mailled;
s8 brightness;
u8 bluetooth;
- u8 turbo;
+ bool turbo;
u8 cpu_fans;
u8 gpu_fans;
- u8 predator_v4;
- u8 pwm;
+ bool hwmon;
+ bool platform_profile;
+ bool pwm;
};
static struct quirk_entry *quirks;
@@ -421,9 +422,11 @@ static void __init set_quirks(void)
interface->capability |= ACER_CAP_TURBO_OC | ACER_CAP_TURBO_LED
| ACER_CAP_TURBO_FAN;
- if (quirks->predator_v4)
- interface->capability |= ACER_CAP_PLATFORM_PROFILE |
- ACER_CAP_HWMON;
+ if (quirks->hwmon)
+ interface->capability |= ACER_CAP_HWMON;
+
+ if (quirks->platform_profile)
+ interface->capability |= ACER_CAP_PLATFORM_PROFILE;
if (quirks->pwm)
interface->capability |= ACER_CAP_PWM;
@@ -451,39 +454,55 @@ static struct quirk_entry quirk_acer_aspire_1520 = {
.brightness = -1,
};
+static struct quirk_entry quirk_acer_aspire_a315_58g = {
+ .hwmon = true,
+};
+
static struct quirk_entry quirk_acer_travelmate_2490 = {
.mailled = 1,
};
static struct quirk_entry quirk_acer_nitro_an515_58 = {
- .predator_v4 = 1,
- .pwm = 1,
+ .hwmon = true,
+ .platform_profile = true,
+ .pwm = true,
};
static struct quirk_entry quirk_acer_predator_ph315_53 = {
- .turbo = 1,
+ .turbo = true,
+ .cpu_fans = 1,
+ .gpu_fans = 1,
+};
+
+static struct quirk_entry quirk_acer_nitro_anv15_51 = {
.cpu_fans = 1,
.gpu_fans = 1,
+ .hwmon = true,
+ .platform_profile = true,
+ .pwm = true,
};
static struct quirk_entry quirk_acer_predator_ph16_72 = {
- .turbo = 1,
+ .turbo = true,
.cpu_fans = 1,
.gpu_fans = 1,
- .predator_v4 = 1,
- .pwm = 1,
+ .hwmon = true,
+ .platform_profile = true,
+ .pwm = true,
};
static struct quirk_entry quirk_acer_predator_pt14_51 = {
- .turbo = 1,
+ .turbo = true,
.cpu_fans = 1,
.gpu_fans = 1,
- .predator_v4 = 1,
- .pwm = 1,
+ .hwmon = true,
+ .platform_profile = true,
+ .pwm = true,
};
static struct quirk_entry quirk_acer_predator_v4 = {
- .predator_v4 = 1,
+ .hwmon = true,
+ .platform_profile = true,
};
/* This AMW0 laptop has no bluetooth */
@@ -637,6 +656,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
{
.callback = dmi_matched,
+ .ident = "Acer Aspire A315-58G",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"),
+ },
+ .driver_data = &quirk_acer_aspire_a315_58g,
+ },
+ {
+ .callback = dmi_matched,
.ident = "Acer TravelMate 2490",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
@@ -655,6 +683,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
{
.callback = dmi_matched,
+ .ident = "Acer Nitro AN16-41",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN16-41"),
+ },
+ .driver_data = &quirk_acer_nitro_an515_58,
+ },
+ {
+ .callback = dmi_matched,
.ident = "Acer Nitro AN515-58",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
@@ -664,6 +701,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
{
.callback = dmi_matched,
+ .ident = "Acer Nitro ANV15-51",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"),
+ },
+ .driver_data = &quirk_acer_nitro_anv15_51,
+ },
+ {
+ .callback = dmi_matched,
.ident = "Acer Predator PH315-53",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
@@ -839,7 +885,6 @@ static const struct dmi_system_id non_acer_quirks[] __initconst = {
};
static struct device *platform_profile_device;
-static bool platform_profile_support;
/*
* The profile used before turbo mode. This variable is needed for
@@ -2194,13 +2239,11 @@ static const struct platform_profile_ops acer_predator_v4_platform_profile_ops =
static int acer_platform_profile_setup(struct platform_device *device)
{
- if (quirks->predator_v4) {
+ if (quirks->platform_profile) {
platform_profile_device = devm_platform_profile_register(
&device->dev, "acer-wmi", NULL, &acer_predator_v4_platform_profile_ops);
if (IS_ERR(platform_profile_device))
return PTR_ERR(platform_profile_device);
-
- platform_profile_support = true;
}
return 0;
}
@@ -2211,7 +2254,7 @@ static int acer_thermal_profile_change(void)
* This mode key will either cycle through each mode or toggle the
* most performant profile.
*/
- if (quirks->predator_v4) {
+ if (quirks->platform_profile) {
u8 current_tp;
int err, tp;
@@ -2530,8 +2573,13 @@ static void acer_wmi_notify(union acpi_object *obj, void *context)
acer_kbd_dock_event(&return_value);
break;
case WMID_GAMING_TURBO_KEY_EVENT:
- if (return_value.key_num == 0x4)
- acer_toggle_turbo();
+ if (return_value.key_num == 0x4) {
+ /* Models without a turbo button report their mode key as 0x4 */
+ if (has_cap(ACER_CAP_TURBO_LED))
+ acer_toggle_turbo();
+ else if (has_cap(ACER_CAP_PLATFORM_PROFILE))
+ acer_thermal_profile_change();
+ }
if (return_value.key_num == 0x5 && has_cap(ACER_CAP_PLATFORM_PROFILE))
acer_thermal_profile_change();
break;
diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c
index e0ebcb0c4acd..a923345dd1cf 100644
--- a/drivers/platform/x86/amd/hfi/hfi.c
+++ b/drivers/platform/x86/amd/hfi/hfi.c
@@ -20,6 +20,7 @@
#include <linux/module.h>
#include <linux/mailbox_client.h>
#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/percpu-defs.h>
#include <linux/platform_device.h>
#include <linux/smp.h>
@@ -38,8 +39,6 @@
#define AMD_HETERO_CPUID_27 0x80000027
-static struct platform_device *device;
-
/**
* struct amd_shmem_info - Shared memory table for AMD HFI
*
@@ -76,6 +75,8 @@ struct amd_hfi_data {
struct amd_shmem_info *shmem;
struct dentry *dbgfs_dir;
+
+ int cpuhp_state;
};
/**
@@ -166,6 +167,21 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
return -EINVAL;
}
+ /*
+ * The bitmaps enumerating the APIC IDs occupy the first n_bitmaps
+ * words of table_data. Reject a firmware-provided count that would
+ * push those reads past the end of the shared memory region.
+ */
+ if (struct_size(amd_hfi_data->shmem, table_data, amd_hfi_data->shmem->n_bitmaps) >
+ pcct_ext->length) {
+ dev_err(amd_hfi_data->dev, "invalid number of bitmaps: %u\n",
+ amd_hfi_data->shmem->n_bitmaps);
+ return -EINVAL;
+ }
+
+ /* The ranking data for each processor follows the bitmaps */
+ u32 *table = amd_hfi_data->shmem->table_data + amd_hfi_data->shmem->n_bitmaps;
+
for (unsigned int i = 0; i < amd_hfi_data->shmem->n_bitmaps; i++) {
u32 bitmap = amd_hfi_data->shmem->table_data[i];
@@ -190,10 +206,6 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu_index);
apic_index = apic_start * info->nr_class * 2;
for (unsigned int k = 0; k < info->nr_class; k++) {
- u32 *table = amd_hfi_data->shmem->table_data +
- amd_hfi_data->shmem->n_bitmaps +
- i * info->nr_class;
-
info->amd_hfi_classes[k].eff = table[apic_index + 2 * k];
info->amd_hfi_classes[k].perf = table[apic_index + 2 * k + 1];
}
@@ -244,6 +256,8 @@ static void amd_hfi_remove(struct platform_device *pdev)
{
struct amd_hfi_data *dev = platform_get_drvdata(pdev);
+ cpuhp_remove_state(dev->cpuhp_state);
+ cancel_work_sync(&sched_amd_hfi_itmt_work);
debugfs_remove_recursive(dev->dbgfs_dir);
}
@@ -494,6 +508,7 @@ static int amd_hfi_probe(struct platform_device *pdev)
amd_hfi_online, amd_hfi_offline);
if (ret < 0)
return ret;
+ amd_hfi_data->cpuhp_state = ret;
schedule_work(&sched_amd_hfi_itmt_work);
@@ -523,12 +538,6 @@ static int __init amd_hfi_init(void)
!cpu_feature_enabled(X86_FEATURE_AMD_WORKLOAD_CLASS))
return -ENODEV;
- device = platform_device_register_simple(AMD_HFI_DRIVER, -1, NULL, 0);
- if (IS_ERR(device)) {
- pr_err("unable to register HFI platform device\n");
- return PTR_ERR(device);
- }
-
ret = platform_driver_register(&amd_hfi_driver);
if (ret)
pr_err("failed to register HFI driver\n");
@@ -539,7 +548,6 @@ static int __init amd_hfi_init(void)
static __exit void amd_hfi_exit(void)
{
platform_driver_unregister(&amd_hfi_driver);
- platform_device_unregister(device);
}
module_init(amd_hfi_init);
module_exit(amd_hfi_exit);
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
index ddd7a04ee753..167c76c13ad0 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -300,16 +300,26 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
* so that userspace which expects the file to exist gets a clear
* -EOPNOTSUPP from the read handler instead of -ENOENT, and is
* pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path.
+ * Hide metrics_bin on client platforms.
*/
- if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
+ if (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
return battr->attr.mode;
return 0;
}
+/* Defined below by HSMP_DEV_ATTR() */
+static struct hsmp_sys_attr hattr_smu_fw_version;
+static struct hsmp_sys_attr hattr_protocol_version;
+
static umode_t hsmp_is_sock_dev_attr_visible(struct kobject *kobj,
struct attribute *attr, int id)
{
+ /* Hide every attribute except smu_fw_version and protocol_version on client platforms */
+ if (is_client_platform() && attr != &hattr_smu_fw_version.dattr.attr &&
+ attr != &hattr_protocol_version.dattr.attr)
+ return 0;
+
return attr->mode;
}
@@ -557,15 +567,26 @@ static int init_acpi(struct device *dev)
return ret;
}
- if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) {
+ /*
+ * proto_ver holds the Ryzen Master interface version on client
+ * platforms, a separate numbering space from enum
+ * hsmp_proto_versions; gate the two platforms on their own
+ * version enum rather than relying on numeric coincidence
+ * between the two.
+ */
+ if ((is_client_platform() && hsmp_pdev->proto_ver >= RYZEN_MASTER_PROTO_VER1) ||
+ (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)) {
ret = hsmp_get_tbl_dram_base(sock_ind);
if (ret)
dev_info(dev, "Failed to init metric table\n");
}
- ret = hsmp_create_sensor(dev, sock_ind);
- if (ret)
- dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
+ /* Skip hwmon registration on client platforms */
+ if (!is_client_platform()) {
+ ret = hsmp_create_sensor(dev, sock_ind);
+ if (ret)
+ dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
+ }
dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index 5e123a4ecea9..45d20beda946 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -10,7 +10,10 @@
#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/build_bug.h>
#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
@@ -45,8 +48,100 @@
*/
#define CHECK_GET_BIT BIT(31)
+/* Catch a table left out of sync with its enum at build time */
+static_assert(ARRAY_SIZE(hsmp_msg_desc_table) == HSMP_MSG_ID_MAX);
+
+/* Per-platform message set: which table to use, and driver-issued msg IDs */
+struct hsmp_plat_desc {
+ const struct hsmp_msg_desc *msg_desc;
+ u32 num_msgs;
+ u32 test_msg;
+ u32 proto_ver_msg;
+ u32 metric_tbl_msg;
+ u32 metric_dram_msg;
+};
+
+static const struct hsmp_plat_desc hsmp_desc_server = {
+ .msg_desc = hsmp_msg_desc_table,
+ .num_msgs = HSMP_MSG_ID_MAX,
+ .test_msg = HSMP_TEST,
+ .proto_ver_msg = HSMP_GET_PROTO_VER,
+ .metric_tbl_msg = HSMP_GET_METRIC_TABLE,
+ .metric_dram_msg = HSMP_GET_METRIC_TABLE_DRAM_ADDR,
+};
+
+/*
+ * The client drives a different mailbox with the Ryzen Master SMC message
+ * set. It has no per-message descriptor table: num_args/response_sz are
+ * bounded generically instead (see validate_message()), and an unsupported
+ * or malformed msg_id is rejected by firmware with its own SMU status code.
+ */
+static const struct hsmp_plat_desc hsmp_desc_client = {
+ .msg_desc = NULL,
+ .num_msgs = HSMP_CLIENT_MSG_ID_MAX,
+ .test_msg = HSMP_CLIENT_TEST,
+ .proto_ver_msg = HSMP_CLIENT_GET_INTERFACE_VER,
+ .metric_tbl_msg = HSMP_CLIENT_GET_METRICS_TABLE,
+ .metric_dram_msg = HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR,
+};
+
static struct hsmp_plat_device hsmp_pdev;
+/* Cached on first use; READ_ONCE()/WRITE_ONCE() avoid a torn access */
+static const struct hsmp_plat_desc *hsmp_desc_cache;
+
+static const struct hsmp_plat_desc *hsmp_desc(void)
+{
+ const struct hsmp_plat_desc *desc = READ_ONCE(hsmp_desc_cache);
+
+ if (likely(desc))
+ return desc;
+
+ desc = is_client_platform() ? &hsmp_desc_client : &hsmp_desc_server;
+ WRITE_ONCE(hsmp_desc_cache, desc);
+
+ return desc;
+}
+
+/* Returns NULL if the platform has no descriptor table, or msg_id is out of range or reserved */
+static const struct hsmp_msg_desc *get_msg_desc(u32 msg_id)
+{
+ const struct hsmp_plat_desc *desc = hsmp_desc();
+
+ if (!desc->msg_desc)
+ return NULL;
+
+ if (msg_id < desc->test_msg || msg_id >= desc->num_msgs)
+ return NULL;
+
+ /*
+ * Sanitize msg_id here rather than trusting a caller-side clamp: a
+ * mispredicted branch upstream can still reach this function with
+ * an unclamped msg_id (Spectre v1, CVE-2017-5753).
+ */
+ msg_id = array_index_nospec(msg_id, desc->num_msgs);
+
+ if (desc->msg_desc[msg_id].type == HSMP_RSVD)
+ return NULL;
+
+ return &desc->msg_desc[msg_id];
+}
+
+/*
+ * Response size for a message the driver issues directly, such as the
+ * probe-time test, protocol-version and metric-table messages: the
+ * descriptor table entry for server, since these fixed, well-known message
+ * IDs always have one; the client protocol max otherwise, since the client
+ * set has no descriptor table and firmware reports no response length.
+ * Callers only consume the response words they expect.
+ */
+static u32 hsmp_msg_response_sz(u32 msg_id)
+{
+ const struct hsmp_msg_desc *desc = get_msg_desc(msg_id);
+
+ return desc ? desc->response_sz : HSMP_CLIENT_MAX_MSG_LEN;
+}
+
/*
* Gates the AMD HSMP data plane against socket bring-up and teardown.
*
@@ -184,30 +279,46 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms
static int validate_message(struct hsmp_message *msg)
{
- /* msg_id against valid range of message IDs */
- if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
- return -ENOMSG;
+ const struct hsmp_plat_desc *plat_desc = hsmp_desc();
+ const struct hsmp_msg_desc *desc;
+
+ if (!plat_desc->msg_desc) {
+ /*
+ * The client message set has no per-message descriptor
+ * table and no msg_id range to validate against; cap
+ * num_args/response_sz to what the Ryzen Master mailbox
+ * supports in each direction and leave msg_id correctness,
+ * such as an unsupported or out-of-range value, to the SMU
+ * status code __hsmp_send_message() already translates.
+ */
+ if (msg->num_args > HSMP_CLIENT_MAX_MSG_LEN ||
+ msg->response_sz > HSMP_CLIENT_MAX_MSG_LEN)
+ return -EINVAL;
+
+ return 0;
+ }
- /* msg_id is a reserved message ID */
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
+ /* Out-of-range or reserved message ID for this platform */
+ desc = get_msg_desc(msg->msg_id);
+ if (!desc)
return -ENOMSG;
/*
* num_args passed by user should match the num_args specified in
* message description table.
*/
- if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args)
+ if (msg->num_args != desc->num_args)
return -EINVAL;
/*
* As the HSMP protocol evolves, newer platforms may define more
* response arguments for existing messages. Use an upper-bound
* check so that older userspace callers requesting fewer response
- * words than what the current hsmp_msg_desc_table[] defines are
- * still accepted, while rejecting requests that exceed the
- * hardware capability.
+ * words than what the current descriptor table defines are still
+ * accepted, while rejecting requests that exceed the hardware
+ * capability.
*/
- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
+ if (msg->response_sz > desc->response_sz)
return -EINVAL;
return 0;
@@ -316,7 +427,7 @@ int hsmp_test(u16 sock_ind, u32 value)
* Test the hsmp port by performing TEST command. The test message
* takes one argument and returns the value of that argument + 1.
*/
- msg.msg_id = HSMP_TEST;
+ msg.msg_id = hsmp_desc()->test_msg;
msg.num_args = 1;
msg.response_sz = 1;
msg.args[0] = value;
@@ -338,13 +449,12 @@ int hsmp_test(u16 sock_ind, u32 value)
}
EXPORT_SYMBOL_NS_GPL(hsmp_test, "AMD_HSMP");
-static bool is_get_msg(struct hsmp_message *msg)
+static bool is_get_msg(const struct hsmp_msg_desc *desc, struct hsmp_message *msg)
{
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_GET)
+ if (desc->type == HSMP_GET)
return true;
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET &&
- (msg->args[0] & CHECK_GET_BIT))
+ if (desc->type == HSMP_SET_GET && (msg->args[0] & CHECK_GET_BIT))
return true;
return false;
@@ -354,63 +464,65 @@ static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
+ const struct hsmp_plat_desc *plat_desc = hsmp_desc();
+ const struct hsmp_msg_desc *desc = NULL;
int ret;
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
return -EFAULT;
/*
- * Check msg_id is within the range of supported msg ids
- * i.e within the array bounds of hsmp_msg_desc_table
+ * The client message set has no per-message descriptor table to
+ * bounds-check msg_id against or index into, and no per-message
+ * type to gate fd mode on, so none of that applies here.
+ * validate_message() (called via hsmp_send_message() below) still
+ * bounds num_args/response_sz generically, and firmware is the
+ * final arbiter of msg_id validity via its own response status.
*/
- if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
- return -ENOMSG;
-
- /*
- * Sanitize the user-controlled msg_id against speculative
- * execution. The bounds check above retires the out-of-range
- * case with -ENOMSG, but a mispredicted branch can still let the
- * CPU speculatively use msg_id as an index into
- * hsmp_msg_desc_table[] (here and in validate_message() /
- * is_get_msg() called downstream via hsmp_send_message()), and
- * pull arbitrary kernel memory into the cache (Spectre v1,
- * CVE-2017-5753). Clamp once into msg.msg_id so every downstream
- * dereference sees the sanitized value.
- */
- msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX);
-
- switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
- case FMODE_WRITE:
- /*
- * Device is opened in O_WRONLY mode
- * Execute only set/configure commands
- */
- if (is_get_msg(&msg))
- return -EPERM;
- break;
- case FMODE_READ:
- /*
- * Device is opened in O_RDONLY mode
- * Execute only get/monitor commands
- */
- if (!is_get_msg(&msg))
+ if (plat_desc->msg_desc) {
+ /* Rejects out-of-range and reserved msg_ids */
+ desc = get_msg_desc(msg.msg_id);
+ if (!desc)
+ return -ENOMSG;
+
+ switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
+ case FMODE_WRITE:
+ /*
+ * Device is opened in O_WRONLY mode
+ * Execute only set/configure commands.
+ */
+ if (is_get_msg(desc, &msg))
+ return -EPERM;
+ break;
+ case FMODE_READ:
+ /*
+ * Device is opened in O_RDONLY mode
+ * Execute only get/monitor commands.
+ */
+ if (!is_get_msg(desc, &msg))
+ return -EPERM;
+ break;
+ case FMODE_READ | FMODE_WRITE:
+ /*
+ * Device is opened in O_RDWR mode
+ * Execute both get/monitor and set/configure commands
+ */
+ break;
+ default:
return -EPERM;
- break;
- case FMODE_READ | FMODE_WRITE:
- /*
- * Device is opened in O_RDWR mode
- * Execute both get/monitor and set/configure commands
- */
- break;
- default:
- return -EPERM;
+ }
}
ret = hsmp_send_message(&msg);
if (ret)
return ret;
- if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
+ /*
+ * The client message set has no per-message response_sz to check
+ * here (desc is NULL); validate_message() already bounded
+ * msg.response_sz.
+ */
+ if ((desc ? desc->response_sz : msg.response_sz) > 0) {
/* Copy results back to user for get/monitor commands */
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
return -EFAULT;
@@ -565,11 +677,11 @@ static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
return -EINVAL;
}
- msg.msg_id = HSMP_GET_METRIC_TABLE;
+ msg.msg_id = hsmp_desc()->metric_tbl_msg;
msg.sock_ind = sock->sock_ind;
/*
- * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared
+ * The metric table message makes firmware refill this socket's shared
* metric DRAM region, which is then copied out below. Hold the
* per-socket lock across the fill-and-copy so concurrent readers of the
* same socket cannot return a torn snapshot.
@@ -635,8 +747,8 @@ int hsmp_get_tbl_dram_base(u16 sock_ind)
int ret;
msg.sock_ind = sock_ind;
- msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
- msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
+ msg.msg_id = hsmp_desc()->metric_dram_msg;
+ msg.response_sz = hsmp_msg_response_sz(msg.msg_id);
ret = hsmp_send_message_locked(&msg);
if (ret)
@@ -687,9 +799,9 @@ int hsmp_cache_proto_ver(u16 sock_ind)
struct hsmp_message msg = { 0 };
int ret;
- msg.msg_id = HSMP_GET_PROTO_VER;
+ msg.msg_id = hsmp_desc()->proto_ver_msg;
+ msg.response_sz = hsmp_msg_response_sz(msg.msg_id);
msg.sock_ind = sock_ind;
- msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
ret = hsmp_send_message_locked(&msg);
if (!ret)
@@ -720,7 +832,13 @@ int hsmp_misc_register(struct device *dev)
*/
hsmp_pdev.mdev.parent = dev;
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
- hsmp_pdev.mdev.mode = 0644;
+ /*
+ * hsmp_msg_desc_table[] only describes server messages. On client
+ * platforms, msg_id mappings differ, making access checks unreliable.
+ * Restrict /dev/hsmp to root until client-specific validation is
+ * added.
+ */
+ hsmp_pdev.mdev.mode = is_client_platform() ? 0600 : 0644;
return misc_register(&hsmp_pdev.mdev);
}
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index 8dbff16a87b1..3302c1517369 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -10,6 +10,9 @@
#ifndef HSMP_H
#define HSMP_H
+#include <asm/amd/hsmp.h>
+
+#include <linux/acpi.h>
#include <linux/compiler_types.h>
#include <linux/device.h>
#include <linux/hwmon.h>
@@ -17,6 +20,7 @@
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/processor.h>
#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
@@ -32,6 +36,26 @@
#define DRIVER_VERSION "2.6"
+/* True when the ACPI-reported PM profile indicates a client platform */
+static inline bool is_client_platform(void)
+{
+ if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
+ return false;
+
+#if IS_ENABLED(CONFIG_ACPI)
+ switch (acpi_gbl_FADT.preferred_profile) {
+ case PM_DESKTOP:
+ case PM_MOBILE:
+ case PM_TABLET:
+ return true;
+ default:
+ return false;
+ }
+#else
+ return false;
+#endif
+}
+
struct hsmp_mbaddr_info {
u32 base_addr;
u32 msg_id_off;
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index 09a30f333759..fed6c51879fc 100644
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -143,6 +143,14 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_BOARD_NAME, "RC73YA"),
}
},
+ {
+ .ident = "ASUS ROG Flow Z13 GZ302EA",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
+ DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"),
+ }
+ },
/* https://bugzilla.kernel.org/show_bug.cgi?id=218024 */
{
.ident = "V14 G4 AMN",
@@ -254,6 +262,15 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "21C6"),
}
},
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */
+ {
+ .ident = "Thinkpad L14 Gen3",
+ .driver_data = &quirk_s2idle_bug,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21C5"),
+ }
+ },
/* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */
{
.ident = "Lenovo Yoga 6 13ALC6",
@@ -280,6 +297,15 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "83N1"),
}
},
+ {
+ .ident = "HP Laptop 14-fq1xxx",
+ .driver_data = &quirk_s2idle_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Laptop 14-fq1xxx"),
+ DMI_MATCH(DMI_BOARD_NAME, "887C"),
+ }
+ },
/* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */
{
.ident = "HP Laptop 15s-eq2xxx",
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index 6792aa2c6187..ef07b24bfeba 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -182,7 +182,6 @@ static const struct amd_pmc_cpu_info amd_1ah_m80_cpu_info = {
.smu_arg = AMD_PMC_REGISTER_ARG_1AH_80H,
.smu_rsp = AMD_PMC_REGISTER_RSP_1AH_80H,
.num_ips = ARRAY_SIZE(soc15_ip_blk_v3),
- .scratch_reg = AMD_PMC_SCRATCH_REG_1AH,
.ips_ptr = soc15_ip_blk_v3,
.os_hint = MSG_OS_HINT_RN,
};
@@ -199,6 +198,7 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, NULL) },
{ PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, NULL) },
{ PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_1ah_m80_cpu_info) },
+ { PCI_DEVICE_DATA(AMD, 1AH_MDCH_ROOT, &amd_1ah_m80_cpu_info) },
{ }
};
@@ -517,8 +517,9 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
&smu_fw_info_fops);
debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev,
&s0ix_stats_fops);
- debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
- &amd_pmc_idlemask_fops);
+ if (dev->cpu_info->scratch_reg)
+ debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
+ &amd_pmc_idlemask_fops);
}
static char *amd_pmc_get_msg_port(struct amd_pmc_dev *dev)
@@ -701,19 +702,6 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
return rc;
}
-static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev)
-{
- /*
- * Starting a new HW sleep cycle right after waking from one
- * can cause electrical problems triggering the over voltage protection.
- * That is avoided by delaying the next suspend a bit, see also
- * https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/
- */
- struct smu_metrics table;
-
- return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status;
-}
-
static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev)
{
/*
@@ -804,7 +792,7 @@ static void amd_pmc_s2idle_check(void)
struct amd_pmc_dev *pdev = &pmc;
int rc;
- if (amd_pmc_intermediate_wakeup_need_delay(pdev) ||
+ if (!pdev->is_first_check_after_suspend ||
amd_pmc_want_suspend_delay(pdev))
msleep(2500);
@@ -970,6 +958,10 @@ static void amd_pmc_remove(struct platform_device *pdev)
pci_dev_put(dev->rdev);
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
amd_mp2_stb_deinit(dev);
+ dev->regbase = NULL;
+ dev->smu_virt_addr = NULL;
+ dev->stb_virt_addr = NULL;
+ dev->fch_virt_addr = NULL;
}
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h
index 6973a639d7e3..4633abf2cca1 100644
--- a/drivers/platform/x86/amd/pmc/pmc.h
+++ b/drivers/platform/x86/amd/pmc/pmc.h
@@ -194,6 +194,7 @@ void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b
+#define PCI_DEVICE_ID_AMD_1AH_MDCH_ROOT 0x1b20
#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
int amd_stb_s2d_init(struct amd_pmc_dev *dev);
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index 3d94b03cf794..4392bf0bc0bf 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -50,7 +50,7 @@ static union acpi_object *apmf_if_call(struct amd_pmf_dev *pdev, int fn, struct
return buffer.pointer;
}
-static int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
+int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
{
union acpi_object *info;
size_t size;
@@ -121,8 +121,7 @@ static union acpi_object *apts_if_call(struct amd_pmf_dev *pdev, u32 state_index
return buffer.pointer;
}
-static int apts_if_call_store_buffer(struct amd_pmf_dev *pdev,
- u32 index, void *data, size_t out_sz)
+int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz)
{
union acpi_object *info;
size_t size;
@@ -158,46 +157,6 @@ out:
return err;
}
-int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
-{
- /* If bit-n is set, that indicates function n+1 is supported */
- return !!(pdev->supported_func & BIT(index - 1));
-}
-
-int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
-{
- return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
-}
-
-int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
- struct amd_pmf_apts_granular_output *data, u32 apts_idx)
-{
- if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
- return -EINVAL;
-
- return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data));
-}
-
-int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
- struct apmf_static_slider_granular_output_v2 *data)
-{
- if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
- return -EINVAL;
-
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
- data, sizeof(*data));
-}
-
-int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
- struct apmf_static_slider_granular_output *data)
-{
- if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
- return -EINVAL;
-
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
- data, sizeof(*data));
-}
-
int apmf_os_power_slider_update(struct amd_pmf_dev *pdev, u8 event)
{
struct os_power_slider args;
@@ -313,27 +272,6 @@ static int apmf_notify_smart_pc_update(struct amd_pmf_dev *pdev, u32 val, u32 pr
return 0;
}
-int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
-}
-
-int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
-}
-
-int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
-}
-
-int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
- req, sizeof(*req));
-}
-
/* Store custom BIOS inputs data in ring buffer */
static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
{
@@ -495,16 +433,6 @@ static int apmf_get_system_params(struct amd_pmf_dev *dev)
return 0;
}
-int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data));
-}
-
-int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
-{
- return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
-}
-
static apmf_event_handler_t apmf_event_handlers[] = {
[PMF_IF_V1] = apmf_event_handler_v1,
[PMF_IF_V2] = apmf_event_handler_v2,
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index b4eae65e675b..ba9324448586 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -138,14 +138,6 @@ static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev)
&current_power_limits_fops);
}
-int amd_pmf_get_power_source(void)
-{
- if (power_supply_is_system_supplied() > 0)
- return POWER_SOURCE_AC;
- else
- return POWER_SOURCE_DC;
-}
-
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
{
return ioread32(dev->regbase + reg_offset);
@@ -176,20 +168,6 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
-/**
- * fixp_q88_fromint: Convert integer to Q8.8
- * @val: input value
- *
- * Converts an integer into binary fixed point format where 8 bits
- * are used for integer and 8 bits are used for the decimal.
- *
- * Return: unsigned integer converted to Q8.8 format
- */
-u32 fixp_q88_fromint(u32 val)
-{
- return val << 8;
-}
-
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
{
int rc;
@@ -454,6 +432,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0107", 0},
{"AMDI0108", 0},
{"AMDI0109", 0},
+ {"AMDI0112", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 07ec00684233..9f64ab833c08 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -13,11 +13,14 @@
#include <linux/acpi.h>
#include <linux/amd-pmf-io.h>
+#include <linux/bits.h>
#include <linux/circ_buf.h>
#include <linux/compiler_attributes.h>
#include <linux/compiler_types.h>
+#include <linux/errno.h>
#include <linux/input.h>
#include <linux/mutex_types.h>
+#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/types.h>
@@ -1073,18 +1076,49 @@ struct ta_pmf_shared_memory {
/* Core Layer */
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev);
void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev);
-int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index);
+int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz);
+int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz);
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data);
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev);
-int amd_pmf_get_power_source(void);
int apmf_install_handler(struct amd_pmf_dev *pmf_dev);
int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
-u32 fixp_q88_fromint(u32 val);
-int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
void amd_pmf_set_device(struct device *p_device);
+static inline int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
+{
+ /* If bit-n is set, that indicates function n+1 is supported */
+ return !!(pdev->supported_func & BIT(index - 1));
+}
+
+static inline int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
+{
+ return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
+}
+
+static inline int amd_pmf_get_power_source(void)
+{
+ if (power_supply_is_system_supplied() > 0)
+ return POWER_SOURCE_AC;
+ else
+ return POWER_SOURCE_DC;
+}
+
+/**
+ * fixp_q88_fromint: Convert integer to Q8.8
+ * @val: input value
+ *
+ * Converts an integer into binary fixed point format where 8 bits
+ * are used for integer and 8 bits are used for the decimal.
+ *
+ * Return: unsigned integer converted to Q8.8 format
+ */
+static inline u32 fixp_q88_fromint(u32 val)
+{
+ return val << 8;
+}
+
/* Metrics layer */
int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev);
@@ -1093,42 +1127,100 @@ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
struct amd_pmf_static_slider_granular *table);
int amd_pmf_init_sps(struct amd_pmf_dev *dev);
-int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
- struct apmf_static_slider_granular_output *output);
-bool is_pprof_balanced(struct amd_pmf_dev *pmf);
int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev);
const char *amd_pmf_source_as_str(unsigned int state);
-const char *amd_pmf_source_as_str(unsigned int state);
-
int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx);
int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf);
-int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev,
- struct apmf_static_slider_granular_output_v2 *data);
-int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
- struct amd_pmf_apts_granular_output *data, u32 apts_idx);
+
+static inline int
+apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
+ struct amd_pmf_apts_granular_output *data, u32 apts_idx)
+{
+ if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ return -EINVAL;
+
+ return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data));
+}
+
+static inline int
+apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
+ struct apmf_static_slider_granular_output_v2 *data)
+{
+ if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ return -EINVAL;
+
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
+ data, sizeof(*data));
+}
+
+static inline int
+apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
+ struct apmf_static_slider_granular_output *data)
+{
+ if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ return -EINVAL;
+
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
+ data, sizeof(*data));
+}
+
+static inline bool is_pprof_balanced(struct amd_pmf_dev *pmf)
+{
+ return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
+}
/* Auto Mode Layer */
-int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data);
void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
-int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
-int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req);
-int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req);
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
int amd_pmf_reset_amt(struct amd_pmf_dev *dev);
void amd_pmf_handle_amt(struct amd_pmf_dev *dev);
+static inline int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
+}
+
+static inline int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev,
+ struct apmf_sbios_req_v2 *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
+}
+
+static inline int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev,
+ struct apmf_sbios_req_v1 *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
+}
+
+static inline int apmf_get_sbios_requests(struct amd_pmf_dev *pdev,
+ struct apmf_sbios_req *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
+ req, sizeof(*req));
+}
+
/* CnQF Layer */
-int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
-int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
int amd_pmf_init_cnqf(struct amd_pmf_dev *dev);
void amd_pmf_deinit_cnqf(struct amd_pmf_dev *dev);
int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_lapsed_ms);
extern const struct attribute_group cnqf_feature_attribute_group;
+static inline int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev,
+ struct apmf_dyn_slider_output *data)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data));
+}
+
+static inline int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev,
+ struct apmf_dyn_slider_output *data)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
+}
+
/* Smart PC builder Layer */
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev);
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev);
@@ -1137,9 +1229,25 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
/* Smart PC - TA interfaces */
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
-void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
-u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index);
+
+static inline u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
+{
+ switch (index) {
+ case 0 ... 1:
+ return in->ev_info.bios_input_1[index];
+ case 2 ... 9:
+ return in->ev_info.bios_input_2[index - 2];
+ default:
+ return 0;
+ }
+}
+
+#ifdef CONFIG_AMD_PMF_DEBUG
+void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
+#else
+static inline void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
+#endif
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index 592ba4de4c7f..a12b4ee965a2 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -17,19 +17,6 @@
#include <linux/units.h>
#include "pmf.h"
-u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
-{
- switch (index) {
- case 0 ... 1:
- return in->ev_info.bios_input_1[index];
- case 2 ... 9:
- return in->ev_info.bios_input_2[index - 2];
- default:
- return 0;
- }
-}
-EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs);
-
#ifdef CONFIG_AMD_PMF_DEBUG
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
@@ -60,8 +47,6 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
amd_pmf_get_ta_custom_bios_inputs(in, i));
dev_dbg(dev->dev, "==== TA inputs END ====\n");
}
-#else
-void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
#endif
/*
@@ -199,13 +184,11 @@ static int amd_pmf_get_battery_prop(enum power_supply_property prop)
continue;
ret = power_supply_get_property(psy, prop, &value);
- if (ret) {
- power_supply_put(psy);
- return ret;
- }
+ power_supply_put(psy);
+ return ret ? ret : value.intval;
}
- return value.intval;
+ return -ENODEV;
}
static int amd_pmf_get_battery_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c
index 0b70a5153f46..b0031afae33b 100644
--- a/drivers/platform/x86/amd/pmf/sps.c
+++ b/drivers/platform/x86/amd/pmf/sps.c
@@ -248,20 +248,24 @@ static int amd_pmf_update_sps_power_limits_v2(struct amd_pmf_dev *pdev, int pwr_
switch (pwr_mode) {
case POWER_MODE_PERFORMANCE:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_PERFORMANCE];
- amd_pmf_update_slider_v2(pdev, index);
break;
case POWER_MODE_BALANCED_POWER:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BALANCED];
- amd_pmf_update_slider_v2(pdev, index);
break;
case POWER_MODE_POWER_SAVER:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_POWER_EFFICIENCY];
- amd_pmf_update_slider_v2(pdev, index);
break;
default:
return -EINVAL;
}
+ if (index >= APTS_MAX_STATES) {
+ dev_err(pdev->dev, "Invalid APTS index: %d\n", index);
+ return -EINVAL;
+ }
+
+ amd_pmf_update_slider_v2(pdev, index);
+
return 0;
}
@@ -281,11 +285,6 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf)
return 0;
}
-bool is_pprof_balanced(struct amd_pmf_dev *pmf)
-{
- return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
-}
-
static int amd_pmf_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c
index 4d1a61e4f1b9..2befc1b14842 100644
--- a/drivers/platform/x86/amd/pmf/util.c
+++ b/drivers/platform/x86/amd/pmf/util.c
@@ -123,6 +123,7 @@ static const struct file_operations pmf_if_ops = {
.owner = THIS_MODULE,
.open = amd_pmf_open,
.unlocked_ioctl = amd_pmf_set_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
};
static struct miscdevice amd_pmf_util_if = {
diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c
index fb382de5b57b..a9926c94eea4 100644
--- a/drivers/platform/x86/asus-armoury.c
+++ b/drivers/platform/x86/asus-armoury.c
@@ -94,6 +94,7 @@ struct asus_armoury_priv {
u32 mini_led_dev_id;
u32 gpu_mux_dev_id;
+ u32 dgpu_disable_dev_id;
bool requires_fan_curve;
};
@@ -110,11 +111,6 @@ static struct fw_attrs_group fw_attrs = {
.pending_reboot = false,
};
-struct asus_attr_group {
- const struct attribute_group *attr_group;
- u32 wmi_devid;
-};
-
static void asus_set_reboot_and_signal_event(void)
{
fw_attrs.pending_reboot = true;
@@ -458,6 +454,12 @@ static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj,
return -ENODEV;
}
}
+
+static bool mini_led_mode_group_visible(struct kobject *kobj)
+{
+ return asus_armoury.mini_led_dev_id;
+}
+
ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode");
static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
@@ -471,8 +473,8 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
if (err)
return err;
- if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) {
- err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU);
+ if (asus_armoury.dgpu_disable_dev_id) {
+ err = armoury_get_devstate(NULL, &result, asus_armoury.dgpu_disable_dev_id);
if (err)
return err;
if (result && !optimus) {
@@ -502,6 +504,12 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
return count;
}
ASUS_WMI_SHOW_INT(gpu_mux_mode_current_value, asus_armoury.gpu_mux_dev_id);
+
+static bool gpu_mux_mode_group_visible(struct kobject *kobj)
+{
+ return asus_armoury.gpu_mux_dev_id;
+}
+
ASUS_ATTR_GROUP_BOOL(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode");
static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
@@ -515,18 +523,31 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
if (err)
return err;
- if (asus_armoury.gpu_mux_dev_id) {
- err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
- if (err)
- return err;
- if (!result && disable) {
- pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n");
- return -EBUSY;
+ if (disable) {
+ if (asus_armoury.gpu_mux_dev_id) {
+ err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
+ if (err)
+ return err;
+ if (!result) {
+ pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n");
+ return -EBUSY;
+ }
+ }
+
+ if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU_POWER_STATE)) {
+ err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU_POWER_STATE);
+ if (err)
+ return err;
+ if (result) {
+ pr_warn("Cannot disable dGPU when it is in use\n");
+ return -EBUSY;
+ }
}
}
scoped_guard(mutex, &asus_armoury.egpu_mutex) {
- err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, ASUS_WMI_DEVID_DGPU);
+ err = armoury_set_devstate(attr, disable ? 1 : 0, NULL,
+ asus_armoury.dgpu_disable_dev_id);
if (err)
return err;
}
@@ -535,7 +556,13 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
return count;
}
-ASUS_WMI_SHOW_INT(dgpu_disable_current_value, ASUS_WMI_DEVID_DGPU);
+
+static bool dgpu_disable_group_visible(struct kobject *kobj)
+{
+ return asus_armoury.dgpu_disable_dev_id;
+}
+
+ASUS_WMI_SHOW_INT(dgpu_disable_current_value, asus_armoury.dgpu_disable_dev_id);
ASUS_ATTR_GROUP_BOOL(dgpu_disable, "dgpu_disable", "Disable the dGPU");
/* Values map for eGPU activation requests. */
@@ -683,6 +710,12 @@ static ssize_t egpu_enable_possible_values_show(struct kobject *kobj, struct kob
{
return armoury_attr_enum_list(buf, ARRAY_SIZE(egpu_status_map));
}
+
+static bool egpu_enable_group_visible(struct kobject *kobj)
+{
+ return armoury_has_devstate(ASUS_WMI_DEVID_EGPU);
+}
+
ASUS_ATTR_GROUP_ENUM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)");
/* Device memory available to APU */
@@ -759,6 +792,12 @@ static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_at
{
return armoury_attr_enum_list(buf, ARRAY_SIZE(apu_mem_map));
}
+
+static bool apu_mem_group_visible(struct kobject *kobj)
+{
+ return armoury_has_devstate(ASUS_WMI_DEVID_APU_MEM);
+}
+
ASUS_ATTR_GROUP_ENUM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use");
/* Define helper to access the current power mode tunable values */
@@ -770,92 +809,6 @@ static inline struct rog_tunables *get_current_tunables(void)
return asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC];
}
-/* Simple attribute creation */
-ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n",
- "Show the current mode of charging");
-ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
- "Set the boot POST sound");
-ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
- "Set MCU powersaving mode");
-ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
- "Set the panel refresh overdrive");
-ASUS_ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
- "Set the panel HD mode to UHD<0> or FHD<1>");
-ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness",
- ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS,
- "Set the panel brightness to Off<0> or On<1>");
-ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
- "Show the eGPU connection status");
-ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL,
- "Set the CPU slow package limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT,
- "Set the CPU fast package limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_PL3_FPPT,
- "Set the CPU fastest package limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT,
- "Set the APU package limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT,
- "Set the platform package limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST,
- "Set the Nvidia dynamic boost limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET,
- "Set the Nvidia max thermal limit");
-ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP,
- "Set the additional TGP on top of the base TGP");
-ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP,
- "Read the base TGP value");
-
-/* If an attribute does not require any special case handling add it here */
-static const struct asus_attr_group armoury_attr_groups[] = {
- { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED },
- { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU },
- { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU },
- { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM },
-
- { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL },
- { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT },
- { &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_PL3_FPPT },
- { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT },
- { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT },
- { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST },
- { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET },
- { &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP },
- { &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP },
-
- { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE },
- { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND },
- { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE },
- { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD },
- { &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD },
- { &screen_auto_brightness_attr_group, ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS },
-};
-
-/**
- * is_power_tunable_attr - Determines if an attribute is a power-related tunable
- * @name: The name of the attribute to check
- *
- * This function checks if the given attribute name is related to power tuning.
- *
- * Return: true if the attribute is a power-related tunable, false otherwise
- */
-static bool is_power_tunable_attr(const char *name)
-{
- static const char * const power_tunable_attrs[] = {
- ATTR_PPT_PL1_SPL, ATTR_PPT_PL2_SPPT,
- ATTR_PPT_PL3_FPPT, ATTR_PPT_APU_SPPT,
- ATTR_PPT_PLATFORM_SPPT, ATTR_NV_DYNAMIC_BOOST,
- ATTR_NV_TEMP_TARGET, ATTR_NV_BASE_TGP,
- ATTR_NV_TGP
- };
-
- for (unsigned int i = 0; i < ARRAY_SIZE(power_tunable_attrs); i++) {
- if (!strcmp(name, power_tunable_attrs[i]))
- return true;
- }
-
- return false;
-}
-
/**
* has_valid_limit - Checks if a power-related attribute has a valid limit value
* @name: The name of the attribute to check
@@ -894,14 +847,74 @@ static bool has_valid_limit(const char *name, const struct power_limits *limits)
return limit_value > 0;
}
+/* Simple attribute creation */
+ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n",
+ "Show the current mode of charging");
+ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
+ "Set the boot POST sound");
+ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
+ "Set MCU powersaving mode");
+ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
+ "Set the panel refresh overdrive");
+ASUS_ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
+ "Set the panel HD mode to UHD<0> or FHD<1>");
+ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness",
+ ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS,
+ "Set the panel brightness to Off<0> or On<1>");
+ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
+ "Show the eGPU connection status");
+ASUS_ATTR_GROUP_BOOL_RO(dgpu_power_state, "dgpu_power_state", ASUS_WMI_DEVID_DGPU_POWER_STATE,
+ "Show the dGPU power state (0: D3 cold, 1: D0 active)");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL,
+ "Set the CPU slow package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT,
+ "Set the CPU fast package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_PL3_FPPT,
+ "Set the CPU fastest package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT,
+ "Set the APU package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT,
+ "Set the platform package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST,
+ "Set the Nvidia dynamic boost limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET,
+ "Set the Nvidia max thermal limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP,
+ "Set the additional TGP on top of the base TGP");
+ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP,
+ "Read the base TGP value");
+
+static const struct attribute_group *armoury_attr_groups[] = {
+ &mini_led_mode_attr_group,
+ &gpu_mux_mode_attr_group,
+ &egpu_connected_attr_group,
+ &egpu_enable_attr_group,
+ &dgpu_disable_attr_group,
+ &dgpu_power_state_attr_group,
+ &apu_mem_attr_group,
+
+ &ppt_pl1_spl_attr_group,
+ &ppt_pl2_sppt_attr_group,
+ &ppt_pl3_fppt_attr_group,
+ &ppt_apu_sppt_attr_group,
+ &ppt_platform_sppt_attr_group,
+ &nv_dynamic_boost_attr_group,
+ &nv_temp_target_attr_group,
+ &nv_base_tgp_attr_group,
+ &nv_tgp_attr_group,
+
+ &charge_mode_attr_group,
+ &boot_sound_attr_group,
+ &mcu_powersave_attr_group,
+ &panel_od_attr_group,
+ &panel_hd_mode_attr_group,
+ &screen_auto_brightness_attr_group,
+ NULL
+};
+
static int asus_fw_attr_add(void)
{
- const struct rog_tunables *const ac_rog_tunables =
- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC];
- const struct power_limits *limits;
- bool should_create;
- const char *name;
- int err, i;
+ int err;
asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
NULL, "%s", DRIVER_NAME);
@@ -929,72 +942,27 @@ static int asus_fw_attr_add(void)
else if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE2))
asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
- if (asus_armoury.mini_led_dev_id) {
- err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
- &mini_led_mode_attr_group);
- if (err) {
- pr_err("Failed to create sysfs-group for mini_led\n");
- goto err_remove_file;
- }
- }
-
asus_armoury.gpu_mux_dev_id = 0;
if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX))
asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX;
else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX_VIVO))
asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO;
- if (asus_armoury.gpu_mux_dev_id) {
- err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
- &gpu_mux_mode_attr_group);
- if (err) {
- pr_err("Failed to create sysfs-group for gpu_mux\n");
- goto err_remove_mini_led_group;
- }
- }
-
- for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) {
- if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
- continue;
-
- /* Always create by default, unless PPT is not present */
- should_create = true;
- name = armoury_attr_groups[i].attr_group->name;
-
- /* Check if this is a power-related tunable requiring limits */
- if (ac_rog_tunables && ac_rog_tunables->power_limits &&
- is_power_tunable_attr(name)) {
- limits = ac_rog_tunables->power_limits;
- /* Check only AC: if not present then DC won't be either */
- should_create = has_valid_limit(name, limits);
- if (!should_create)
- pr_debug("Missing max value for tunable %s\n", name);
- }
+ asus_armoury.dgpu_disable_dev_id = 0;
+ if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU))
+ asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_DGPU;
+ else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MODE))
+ asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_GPU_MODE;
- if (should_create) {
- err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
- armoury_attr_groups[i].attr_group);
- if (err) {
- pr_err("Failed to create sysfs-group for %s\n",
- armoury_attr_groups[i].attr_group->name);
- goto err_remove_groups;
- }
- }
+ err = sysfs_create_groups(&asus_armoury.fw_attr_kset->kobj,
+ armoury_attr_groups);
+ if (err) {
+ pr_err("Failed to create firmware attributes groups\n");
+ goto err_remove_file;
}
return 0;
-err_remove_groups:
- while (i--) {
- if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
- armoury_attr_groups[i].attr_group);
- }
- if (asus_armoury.gpu_mux_dev_id)
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
-err_remove_mini_led_group:
- if (asus_armoury.mini_led_dev_id)
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
err_remove_file:
sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
err_destroy_kset:
@@ -1165,19 +1133,8 @@ err_free_tunables:
static void __exit asus_fw_exit(void)
{
- int i;
-
- for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) {
- if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
- armoury_attr_groups[i].attr_group);
- }
-
- if (asus_armoury.gpu_mux_dev_id)
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
-
- if (asus_armoury.mini_led_dev_id)
- sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
+ sysfs_remove_groups(&asus_armoury.fw_attr_kset->kobj,
+ armoury_attr_groups);
sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
kset_unregister(asus_armoury.fw_attr_kset);
diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h
index 0e301d663d0c..331c3bddf013 100644
--- a/drivers/platform/x86/asus-armoury.h
+++ b/drivers/platform/x86/asus-armoury.h
@@ -100,6 +100,43 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
static struct kobj_attribute attr_##_attrname##_##_prop = \
__ASUS_ATTR_RO(_attrname, _prop)
+/*
+ * Every attribute group decides its own visibility through .is_visible():
+ * sysfs hides a named group entirely when its first attribute reports
+ * SYSFS_GROUP_INVISIBLE, so groups are always created and never leave
+ * empty directories behind.
+ */
+#define __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi) \
+ static bool _attrname##_group_visible(struct kobject *kobj) \
+ { \
+ return armoury_has_devstate(_wmi); \
+ } \
+ DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname)
+
+/*
+ * Power tunables are additionally gated on the platform limits actually
+ * defining a max value for them. Only the AC limits are checked: if not
+ * present then DC won't be either.
+ */
+#define __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi) \
+ static bool _attrname##_group_visible(struct kobject *kobj) \
+ { \
+ const struct rog_tunables *tunables = \
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]; \
+ \
+ if (!tunables || !tunables->power_limits) \
+ return armoury_has_devstate(_wmi); \
+ \
+ if (!has_valid_limit(_fsname, tunables->power_limits)) {\
+ pr_debug("Missing max value for tunable %s\n", \
+ _fsname); \
+ return false; \
+ } \
+ \
+ return armoury_has_devstate(_wmi); \
+ } \
+ DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname)
+
#define __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)\
ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
static struct kobj_attribute attr_##_attrname##_current_value = \
@@ -108,6 +145,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
static struct kobj_attribute attr_##_attrname##_type = \
__ASUS_ATTR_RO_AS(type, enum_type_show); \
+ __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \
static struct attribute *_attrname##_attrs[] = { \
&attr_##_attrname##_current_value.attr, \
&attr_##_attrname##_display_name.attr, \
@@ -116,7 +154,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
NULL \
}; \
static const struct attribute_group _attrname##_attr_group = { \
- .name = _fsname, .attrs = _attrname##_attrs \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
}
#define __ATTR_RW_INT_GROUP_ENUM(_attrname, _minv, _maxv, _wmi, _fsname,\
@@ -129,6 +169,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
static struct kobj_attribute attr_##_attrname##_type = \
__ASUS_ATTR_RO_AS(type, enum_type_show); \
+ __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \
static struct attribute *_attrname##_attrs[] = { \
&attr_##_attrname##_current_value.attr, \
&attr_##_attrname##_display_name.attr, \
@@ -137,7 +178,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
NULL \
}; \
static const struct attribute_group _attrname##_attr_group = { \
- .name = _fsname, .attrs = _attrname##_attrs \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
}
/* Boolean style enumeration, base macro. Requires adding show/store */
@@ -168,37 +211,63 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
__ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)
/*
- * Requires <name>_current_value_show(), <name>_current_value_show()
+ * Boolean style group whose whole visibility is decided by
+ * <name>_group_visible(), for attributes backed by a device ID resolved
+ * at probe time.
+ * Requires <name>_current_value_show(), <name>_current_value_store()
+ * and <name>_group_visible()
*/
#define ASUS_ATTR_GROUP_BOOL(_attrname, _fsname, _dispname) \
+ DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \
static struct kobj_attribute attr_##_attrname##_current_value = \
__ASUS_ATTR_RW(_attrname, current_value); \
- __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", "0;1"); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
+ }
/*
- * Requires <name>_current_value_show(), <name>_current_value_show()
- * and <name>_possible_values_show()
+ * Group whose whole visibility is decided by <name>_group_visible(),
+ * for attributes backed by a device ID resolved at probe time.
+ * Requires <name>_current_value_show(), <name>_current_value_store(),
+ * <name>_possible_values_show() and <name>_group_visible()
*/
-#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \
- __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
- static struct kobj_attribute attr_##_attrname##_current_value = \
- __ASUS_ATTR_RW(_attrname, current_value); \
- static struct kobj_attribute attr_##_attrname##_possible_values = \
- __ASUS_ATTR_RO(_attrname, possible_values); \
- static struct kobj_attribute attr_##_attrname##_type = \
- __ASUS_ATTR_RO_AS(type, enum_type_show); \
- static struct attribute *_attrname##_attrs[] = { \
- &attr_##_attrname##_current_value.attr, \
- &attr_##_attrname##_display_name.attr, \
- &attr_##_attrname##_possible_values.attr, \
- &attr_##_attrname##_type.attr, \
- NULL \
- }; \
- static const struct attribute_group _attrname##_attr_group = { \
- .name = _fsname, .attrs = _attrname##_attrs \
+#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \
+ DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RW(_attrname, current_value); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ static struct kobj_attribute attr_##_attrname##_possible_values =\
+ __ASUS_ATTR_RO(_attrname, possible_values); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
}
#define ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \
+ __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \
ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
static struct kobj_attribute attr_##_attrname##_current_value = \
__ASUS_ATTR_RO(_attrname, current_value); \
@@ -211,7 +280,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
&attr_##_attrname##_type.attr, NULL \
}; \
static const struct attribute_group _attrname##_attr_group = { \
- .name = _fsname, .attrs = _attrname##_attrs \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
}
/*
@@ -284,6 +355,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
__ASUS_ATTR_RW(_attr, current_value)
#define ASUS_ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \
+ __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \
__ROG_TUNABLE_RW(_attrname, _wmi); \
__ROG_TUNABLE_SHOW_DEFAULT(_attrname); \
__ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \
@@ -303,7 +375,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
NULL \
}; \
static const struct attribute_group _attrname##_attr_group = { \
- .name = _fsname, .attrs = _attrname##_attrs \
+ .name = _fsname, \
+ .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
+ .attrs = _attrname##_attrs \
}
/* Default is always the maximum value unless *_def is specified */
@@ -715,6 +789,70 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608PM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .nv_dynamic_boost_max = 15,
+ .nv_dynamic_boost_min = 10,
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .nv_tgp_max = 100,
+ .nv_tgp_min = 55,
+ .ppt_pl1_spl_max = 100,
+ .ppt_pl1_spl_min = 45,
+ .ppt_pl2_sppt_max = 115,
+ .ppt_pl2_sppt_min = 60,
+ .ppt_pl3_fppt_max = 135,
+ .ppt_pl3_fppt_min = 80,
+ },
+ .dc_data = &(struct power_limits) {
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_max = 68,
+ .ppt_pl1_spl_min = 35,
+ .ppt_pl2_sppt_max = 71,
+ .ppt_pl2_sppt_min = 36,
+ .ppt_pl3_fppt_max = 74,
+ .ppt_pl3_fppt_min = 70,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608UH"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .nv_dynamic_boost_max = 15,
+ .nv_dynamic_boost_min = 10,
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .nv_tgp_max = 100,
+ .nv_tgp_min = 55,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 90,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl3_fppt_max = 90,
+ .ppt_pl3_fppt_min = 35,
+ },
+ .dc_data = &(struct power_limits) {
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ },
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "FA608UM"),
},
.driver_data = &(struct power_data) {
@@ -751,6 +889,39 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608UP"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .nv_dynamic_boost_max = 15,
+ .nv_dynamic_boost_min = 10,
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .nv_tgp_max = 100,
+ .nv_tgp_min = 55,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 90,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl3_fppt_max = 90,
+ .ppt_pl3_fppt_min = 35,
+ },
+ .dc_data = &(struct power_limits) {
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ },
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "FA608WI"),
},
.driver_data = &(struct power_data) {
@@ -1058,6 +1229,46 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G533QS"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G713QR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA401IHR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA401Q"),
},
.driver_data = &(struct power_data) {
@@ -1104,6 +1315,37 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA402NV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .nv_dynamic_boost_max = 25,
+ .nv_dynamic_boost_min = 5,
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_def = 65,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl3_fppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ },
+ .dc_data = &(struct power_limits) {
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl3_fppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ },
+ },
+ },
+ {
+ .matches = {
// This model is full AMD. No Nvidia dGPU.
DMI_MATCH(DMI_BOARD_NAME, "GA402R"),
},
@@ -1489,6 +1731,37 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605CM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .nv_dynamic_boost_max = 15,
+ .nv_dynamic_boost_min = 5,
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .nv_tgp_max = 95,
+ .nv_tgp_min = 55,
+ .ppt_pl1_spl_def = 85,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl1_spl_min = 45,
+ .ppt_pl2_sppt_def = 110,
+ .ppt_pl2_sppt_max = 95,
+ .ppt_pl2_sppt_min = 56,
+ },
+ .dc_data = &(struct power_limits) {
+ .nv_temp_target_max = 87,
+ .nv_temp_target_min = 75,
+ .ppt_pl1_spl_def = 85,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl2_sppt_def = 110,
+ .ppt_pl2_sppt_max = 95,
+ .ppt_pl2_sppt_min = 32,
+ },
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "GU605CP"),
},
.driver_data = &(struct power_data) {
@@ -1881,6 +2154,33 @@ static const struct dmi_system_id power_limits[] = {
},
},
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GX651AR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 80,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"),
@@ -1990,6 +2290,38 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G614FM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 120,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_def = 140,
+ .ppt_pl2_sppt_max = 165,
+ .ppt_pl3_fppt_min = 65,
+ .ppt_pl3_fppt_def = 140,
+ .ppt_pl3_fppt_max = 165,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 75,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "G614FP"),
},
.driver_data = &(struct power_data) {
@@ -2550,6 +2882,32 @@ static const struct dmi_system_id power_limits[] = {
},
},
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "RC74XA"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 13,
+ .ppt_pl2_sppt_max = 45,
+ .ppt_pl3_fppt_min = 19,
+ .ppt_pl3_fppt_max = 55,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_def = 17,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 13,
+ .ppt_pl2_sppt_def = 21,
+ .ppt_pl2_sppt_max = 45,
+ .ppt_pl3_fppt_min = 19,
+ .ppt_pl3_fppt_def = 26,
+ .ppt_pl3_fppt_max = 55,
+ },
+ },
+ },
{}
};
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
index 79a575d0f5b4..e4f20ab5250a 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -865,7 +865,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr,
len += sysfs_emit_at(page, len, "SFUN value : %#x\n",
(uint) temp);
/*
- * The HWRS method return informations about the hardware.
+ * The HWRS method return information about the hardware.
* 0x80 bit is for WLAN, 0x100 for Bluetooth.
* 0x40 for WWAN, 0x10 for WIMAX.
* The significance of others is yet to be found.
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index aeb461b1644d..3b0f3346dc60 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -137,6 +137,10 @@ static struct quirk_entry quirk_asus_use_lid_flip_devid = {
.tablet_switch_mode = asus_wmi_lid_flip_devid,
};
+static struct quirk_entry quirk_asus_proart_px13 = {
+ .tablet_switch_mode = asus_wmi_lid_flip_devid,
+};
+
static struct quirk_entry quirk_asus_tablet_mode = {
.wmi_backlight_set_devstate = true,
.tablet_switch_mode = asus_wmi_lid_flip_rog_devid,
@@ -501,6 +505,16 @@ static const struct dmi_system_id asus_quirks[] = {
},
{
.callback = dmi_matched,
+ .ident = "ASUS ROG FLOW X13",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ /* Match GV302X* */
+ DMI_MATCH(DMI_PRODUCT_NAME, "GV302X"),
+ },
+ .driver_data = &quirk_asus_tablet_mode,
+ },
+ {
+ .callback = dmi_matched,
.ident = "ASUS ROG FLOW X16",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
@@ -562,6 +576,15 @@ static const struct dmi_system_id asus_quirks[] = {
},
.driver_data = &quirk_asus_z13,
},
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS ProArt PX13",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HN7306WV"),
+ },
+ .driver_data = &quirk_asus_proart_px13,
+ },
{},
};
@@ -573,7 +596,7 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
driver->quirks = quirks;
driver->panel_power = BACKLIGHT_POWER_ON;
- /* overwrite the wapf setting if the wapf paramater is specified */
+ /* overwrite the wapf setting if the wapf parameter is specified */
if (wapf != -1)
quirks->wapf = wapf;
else
diff --git a/drivers/platform/x86/asus-tf103c-dock.c b/drivers/platform/x86/asus-tf103c-dock.c
index 92466477de9a..3be5b96de548 100644
--- a/drivers/platform/x86/asus-tf103c-dock.c
+++ b/drivers/platform/x86/asus-tf103c-dock.c
@@ -772,7 +772,7 @@ static int tf103c_dock_probe(struct i2c_client *client)
IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN,
"dock_irq", dock);
if (ret)
- return dev_err_probe(dev, ret, "requesting dock IRQ");
+ return ret;
dock->hpd_gpio = devm_gpiod_get(dev, "dock_hpd", GPIOD_IN);
if (IS_ERR(dock->hpd_gpio))
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index a65090429ca7..b33ebdb21aa2 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -16,6 +16,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -28,6 +29,7 @@
#include <linux/leds.h>
#include <linux/minmax.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
@@ -127,7 +129,6 @@ module_param(fnlock_default, bool, 0444);
#define NVIDIA_TEMP_MAX 87
#define ASUS_SCREENPAD_BRIGHT_MAX 255
-#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60
#define ASUS_MINI_LED_MODE_MASK 0x03
/* Standard modes for devices with only on/off */
@@ -262,6 +263,7 @@ struct asus_wmi {
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
+ struct led_classdev mute_led;
struct led_classdev camera_led;
struct workqueue_struct *led_workqueue;
struct work_struct tpd_led_work;
@@ -320,7 +322,6 @@ struct asus_wmi {
struct fan_curve_data custom_fan_curves[3];
struct device *ppdev;
- bool platform_profile_support;
// The RSOC controls the maximum charging percentage.
bool battery_rsoc_available;
@@ -330,7 +331,6 @@ struct asus_wmi {
struct hotplug_slot hotplug_slot;
struct mutex hotplug_lock;
- struct mutex wmi_lock;
struct workqueue_struct *hotplug_workqueue;
struct work_struct hotplug_work;
@@ -353,6 +353,20 @@ static void asus_wmi_show_deprecated(void)
/* WMI ************************************************************************/
+/*
+ * Concurrent evaluations of ASUS WMI methods can re-enter firmware
+ * SMI/EC mailbox handling and corrupt the mailbox.
+ */
+static DEFINE_MUTEX(asus_wmi_eval_lock);
+
+static acpi_status asus_wmi_evaluate_method_locked(u32 method_id,
+ struct acpi_buffer *input,
+ struct acpi_buffer *output)
+{
+ guard(mutex)(&asus_wmi_eval_lock);
+ return wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, input, output);
+}
+
static int asus_wmi_evaluate_method3(u32 method_id,
u32 arg0, u32 arg1, u32 arg2, u32 *retval)
{
@@ -367,8 +381,7 @@ static int asus_wmi_evaluate_method3(u32 method_id,
union acpi_object *obj;
u32 tmp = 0;
- status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
- &input, &output);
+ status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x\n",
__func__, method_id, arg0, arg1, arg2);
@@ -419,8 +432,7 @@ static int asus_wmi_evaluate_method5(u32 method_id,
union acpi_object *obj;
u32 tmp = 0;
- status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
- &input, &output);
+ status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
__func__, method_id, arg0, arg1, arg2, arg3, arg4);
@@ -467,8 +479,7 @@ static int asus_wmi_evaluate_method_buf(u32 method_id,
union acpi_object *obj;
int err = 0;
- status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
- &input, &output);
+ status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x\n",
__func__, method_id, arg0, arg1);
@@ -1047,6 +1058,10 @@ static DEVICE_ATTR_RW(gpu_mux_mode);
#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* TUF Laptop Keyboard RGB Modes **********************************************/
+
+#define TUF_KBD_RGB_SPEED_SLOW 0xe1
+#define TUF_KBD_RGB_SPEED_NORMAL 0xeb
+#define TUF_KBD_RGB_SPEED_FAST 0xf5
static ssize_t kbd_rgb_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -1080,16 +1095,16 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
switch (speed) {
case 0:
- speed = 0xe1;
+ speed = TUF_KBD_RGB_SPEED_SLOW;
break;
case 1:
- speed = 0xeb;
+ speed = TUF_KBD_RGB_SPEED_NORMAL;
break;
case 2:
- speed = 0xf5;
+ speed = TUF_KBD_RGB_SPEED_FAST;
break;
default:
- speed = 0xeb;
+ speed = TUF_KBD_RGB_SPEED_NORMAL;
}
err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev,
@@ -1115,14 +1130,19 @@ static const struct attribute_group kbd_rgb_mode_group = {
};
/* TUF Laptop Keyboard RGB State **********************************************/
+
+#define TUF_KBD_RGB_STATE_BOOT BIT(1)
+#define TUF_KBD_RGB_STATE_AWAKE BIT(3)
+#define TUF_KBD_RGB_STATE_SLEEP BIT(5)
+#define TUF_KBD_RGB_STATE_SHUTDOWN BIT(7)
static ssize_t kbd_rgb_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- u32 flags, cmd, boot, awake, sleep, keyboard;
+ u32 flags, cmd, boot, awake, sleep, shutdown;
int err;
- if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5)
+ if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &shutdown) != 5)
return -EINVAL;
if (cmd)
@@ -1130,17 +1150,17 @@ static ssize_t kbd_rgb_state_store(struct device *dev,
flags = 0;
if (boot)
- flags |= BIT(1);
+ flags |= TUF_KBD_RGB_STATE_BOOT;
if (awake)
- flags |= BIT(3);
+ flags |= TUF_KBD_RGB_STATE_AWAKE;
if (sleep)
- flags |= BIT(5);
- if (keyboard)
- flags |= BIT(7);
+ flags |= TUF_KBD_RGB_STATE_SLEEP;
+ if (shutdown)
+ flags |= TUF_KBD_RGB_STATE_SHUTDOWN;
/* 0xbd is the required default arg0 for the method. Nothing happens otherwise */
err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS,
- ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
+ ASUS_WMI_DEVID_TUF_RGB_CMD, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
if (err)
return err;
@@ -1149,7 +1169,7 @@ static ssize_t kbd_rgb_state_store(struct device *dev,
static DEVICE_ATTR_WO(kbd_rgb_state);
static DEVICE_STRING_ATTR_RO(kbd_rgb_state_index, 0444,
- "cmd boot awake sleep keyboard");
+ "cmd boot awake sleep shutdown");
static struct attribute *kbd_rgb_state_attrs[] = {
&dev_attr_kbd_rgb_state.attr,
@@ -2054,6 +2074,16 @@ static int micmute_led_set(struct led_classdev *led_cdev,
return err < 0 ? err : 0;
}
+static int mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ int state = brightness != LED_OFF;
+ int err;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MUTE_LED, state, NULL);
+ return err < 0 ? err : 0;
+}
+
static enum led_brightness camera_led_get(struct led_classdev *led_cdev)
{
struct asus_wmi *asus;
@@ -2084,10 +2114,23 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
led_classdev_unregister(&asus->micmute_led);
+ led_classdev_unregister(&asus->mute_led);
led_classdev_unregister(&asus->camera_led);
if (asus->led_workqueue)
destroy_workqueue(asus->led_workqueue);
+
+ /*
+ * kbd_led is registered lazily by kbd_led_work: now that the
+ * workqueue is destroyed and asus_ref.asus is NULL, the work can
+ * neither run nor be queued anymore, furthermore leaving it to
+ * devres would run the unregister from devres_release_all(),
+ * after .remove() returned and the struct asus_wmi embedding
+ * kbd_led has been freed.
+ */
+ if (asus->kbd_led_registered)
+ devm_led_classdev_unregister(&asus->platform_device->dev,
+ &asus->kbd_led);
}
static int asus_wmi_led_init(struct asus_wmi *asus)
@@ -2176,7 +2219,19 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
asus->micmute_led.default_trigger = "audio-micmute";
rv = led_classdev_register(&asus->platform_device->dev,
- &asus->micmute_led);
+ &asus->micmute_led);
+ if (rv)
+ goto error;
+ }
+
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MUTE_LED)) {
+ asus->mute_led.name = "platform::mute";
+ asus->mute_led.max_brightness = 1;
+ asus->mute_led.brightness_set_blocking = mute_led_set;
+ asus->mute_led.default_trigger = "audio-mute";
+
+ rv = led_classdev_register(&asus->platform_device->dev,
+ &asus->mute_led);
if (rv)
goto error;
}
@@ -2232,9 +2287,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus)
bool absent;
u32 l;
- mutex_lock(&asus->wmi_lock);
blocked = asus_wlan_rfkill_blocked(asus);
- mutex_unlock(&asus->wmi_lock);
mutex_lock(&asus->hotplug_lock);
pci_lock_rescan_remove();
@@ -2436,30 +2489,6 @@ static void asus_rfkill_query(struct rfkill *rfkill, void *data)
rfkill_set_sw_state(priv->rfkill, !result);
}
-static int asus_rfkill_wlan_set(void *data, bool blocked)
-{
- struct asus_rfkill *priv = data;
- struct asus_wmi *asus = priv->asus;
- int ret;
-
- /*
- * This handler is enabled only if hotplug is enabled.
- * In this case, the asus_wmi_set_devstate() will
- * trigger a wmi notification and we need to wait
- * this call to finish before being able to call
- * any wmi method
- */
- mutex_lock(&asus->wmi_lock);
- ret = asus_rfkill_set(data, blocked);
- mutex_unlock(&asus->wmi_lock);
- return ret;
-}
-
-static const struct rfkill_ops asus_rfkill_wlan_ops = {
- .set_block = asus_rfkill_wlan_set,
- .query = asus_rfkill_query,
-};
-
static const struct rfkill_ops asus_rfkill_ops = {
.set_block = asus_rfkill_set,
.query = asus_rfkill_query,
@@ -2478,13 +2507,8 @@ static int asus_new_rfkill(struct asus_wmi *asus,
arfkill->dev_id = dev_id;
arfkill->asus = asus;
- if (dev_id == ASUS_WMI_DEVID_WLAN &&
- asus->driver->quirks->hotplug_wireless)
- *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
- &asus_rfkill_wlan_ops, arfkill);
- else
- *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
- &asus_rfkill_ops, arfkill);
+ *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
+ &asus_rfkill_ops, arfkill);
if (!*rfkill)
return -EINVAL;
@@ -2558,7 +2582,6 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus)
int result = 0;
mutex_init(&asus->hotplug_lock);
- mutex_init(&asus->wmi_lock);
result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan",
RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN);
@@ -3714,6 +3737,33 @@ static int fan_curve_write(struct asus_wmi *asus,
arg1, arg2, arg3, arg4, &ret);
}
+/*
+ * A fan curve is a set of points the firmware interpolates between, so it
+ * only makes sense if neither temperature nor PWM ever decreases along it.
+ */
+static int fan_curve_validate(struct device *dev, struct fan_curve_data *data)
+{
+ u8 *percents = data->percents;
+ u8 *temps = data->temps;
+ int i;
+
+ for (i = 1; i < FAN_CURVE_POINTS; i++) {
+ if (temps[i] < temps[i - 1]) {
+ dev_warn(dev, "fan curve: temperature decreases at point %d (%u < %u)\n",
+ i, temps[i], temps[i - 1]);
+ return -EINVAL;
+ }
+
+ if (percents[i] < percents[i - 1]) {
+ dev_warn(dev, "fan curve: pwm decreases at point %d (%u < %u)\n",
+ i, percents[i], percents[i - 1]);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static ssize_t fan_curve_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
@@ -3798,6 +3848,11 @@ static ssize_t fan_curve_enable_store(struct device *dev,
}
if (data->enabled) {
+ err = fan_curve_validate(dev, data);
+ if (err) {
+ data->enabled = false;
+ return err;
+ }
err = fan_curve_write(asus, data);
if (err)
return err;
@@ -4297,7 +4352,6 @@ static int platform_profile_setup(struct asus_wmi *asus)
return PTR_ERR(asus->ppdev);
}
- asus->platform_profile_support = true;
return 0;
}
@@ -4486,13 +4540,20 @@ static int is_display_toggle(int code)
static int read_screenpad_backlight_power(struct asus_wmi *asus)
{
- int ret;
+ int ret, retval;
- ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
+ ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_POWER, &retval);
if (ret < 0)
return ret;
- /* 1 == powered */
- return ret ? BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF;
+
+ /*
+ * The firmware reports the panel power in the low byte of the
+ * devstate as a raw EC status byte that is non-zero when the
+ * panel is powered; other models report it through
+ * ASUS_WMI_DSTS_STATUS_BIT, which lies inside the same mask.
+ */
+ return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) ?
+ BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF;
}
static int read_screenpad_brightness(struct backlight_device *bd)
@@ -4517,26 +4578,18 @@ static int read_screenpad_brightness(struct backlight_device *bd)
static int update_screenpad_bl_status(struct backlight_device *bd)
{
- u32 ctrl_param = bd->props.brightness;
- int err = 0;
+ int err;
- if (bd->props.power) {
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL);
- if (err < 0)
- return err;
+ if (backlight_is_blank(bd))
+ return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER,
+ 0, NULL);
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL);
- if (err < 0)
- return err;
- }
-
- if (!bd->props.power) {
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL);
- if (err < 0)
- return err;
- }
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL);
+ if (err < 0)
+ return err;
- return err;
+ return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT,
+ backlight_get_brightness(bd), NULL);
}
static const struct backlight_ops asus_screenpad_bl_ops = {
@@ -4550,16 +4603,17 @@ static int asus_screenpad_init(struct asus_wmi *asus)
struct backlight_device *bd;
struct backlight_properties props;
int err, power;
- int brightness = 0;
+ u32 brightness = 0;
- power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
+ power = read_screenpad_backlight_power(asus);
if (power < 0)
return power;
- if (power) {
+ if (power == BACKLIGHT_POWER_ON) {
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness);
if (err < 0)
return err;
+ brightness &= ASUS_WMI_DSTS_BRIGHTNESS_MASK;
}
memset(&props, 0, sizeof(struct backlight_properties));
@@ -4574,7 +4628,6 @@ static int asus_screenpad_init(struct asus_wmi *asus)
}
asus->screenpad_backlight_device = bd;
- asus->driver->screenpad_brightness = brightness;
bd->props.brightness = brightness;
bd->props.power = power;
backlight_update_status(bd);
@@ -5026,9 +5079,8 @@ static int show_call(struct seq_file *m, void *data)
union acpi_object *obj;
acpi_status status;
- status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID,
- 0, asus->debug.method_id,
- &input, &output);
+ status = asus_wmi_evaluate_method_locked(asus->debug.method_id,
+ &input, &output);
if (ACPI_FAILURE(status))
return -EIO;
@@ -5153,7 +5205,7 @@ static int asus_wmi_add(struct platform_device *pdev)
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
- asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
+ asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_CMD);
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
@@ -5421,8 +5473,11 @@ static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = {
static void asus_s2idle_check_register(void)
{
- if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops))
- pr_warn("failed to register LPS0 sleep handler in asus-wmi\n");
+ int ret;
+
+ ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops);
+ if (ret && ret != -ENODEV)
+ pr_warn("failed to register LPS0 sleep handler: %d\n", ret);
}
static void asus_s2idle_check_unregister(void)
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
index 5cd4392b964e..b45f81b38a47 100644
--- a/drivers/platform/x86/asus-wmi.h
+++ b/drivers/platform/x86/asus-wmi.h
@@ -57,7 +57,6 @@ struct quirk_entry {
struct asus_wmi_driver {
int brightness;
int panel_power;
- int screenpad_brightness;
int wlan_ctrl_by_user;
const char *name;
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index 41a24e091248..81dac81262e7 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -79,6 +79,13 @@ static const struct dmi_system_id dmi_table[] = {
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2S"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
},
.driver_data = (void *)&quirk_fan,
diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c
index 3a373184519d..dd1648b7de77 100644
--- a/drivers/platform/x86/bitland-mifs-wmi.c
+++ b/drivers/platform/x86/bitland-mifs-wmi.c
@@ -30,6 +30,7 @@
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/sysfs.h>
+#include <linux/types.h>
#include <linux/unaligned.h>
#include <linux/units.h>
#include <linux/wmi.h>
@@ -39,25 +40,30 @@
#define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF"
enum bitland_mifs_operation {
- WMI_METHOD_GET = 250,
- WMI_METHOD_SET = 251,
+ WMI_METHOD_GET = 0xFA00,
+ WMI_METHOD_SET = 0xFB00,
};
enum bitland_mifs_function {
- WMI_FN_SYSTEM_PER_MODE = 8,
- WMI_FN_GPU_MODE = 9,
- WMI_FN_KBD_TYPE = 10,
- WMI_FN_FN_LOCK = 11,
- WMI_FN_TP_LOCK = 12,
- WMI_FN_FAN_SPEEDS = 13,
- WMI_FN_RGB_KB_MODE = 16,
- WMI_FN_RGB_KB_COLOR = 17,
- WMI_FN_RGB_KB_BRIGHTNESS = 18,
- WMI_FN_SYSTEM_AC_TYPE = 19,
- WMI_FN_MAX_FAN_SWITCH = 20,
- WMI_FN_MAX_FAN_SPEED = 21,
- WMI_FN_CPU_THERMOMETER = 22,
- WMI_FN_CPU_POWER = 23,
+ WMI_FN_SYSTEM_PER_MODE = 0x0800,
+ WMI_FN_GPU_MODE = 0x0900,
+ WMI_FN_KBD_TYPE = 0x0A00,
+ WMI_FN_FN_LOCK = 0x0B00,
+ WMI_FN_TP_LOCK = 0x0C00,
+ WMI_FN_FAN_SPEEDS = 0x0D00,
+ WMI_FN_RGB_KB_MODE = 0x1000,
+ WMI_FN_RGB_KB_COLOR = 0x1100,
+ WMI_FN_RGB_KB_BRIGHTNESS = 0x1200,
+ WMI_FN_SYSTEM_AC_TYPE = 0x1300,
+ WMI_FN_MAX_FAN_SWITCH = 0x1400,
+ WMI_FN_MAX_FAN_SPEED = 0x1500,
+ WMI_FN_CPU_THERMOMETER = 0x1600,
+ WMI_FN_CPU_POWER = 0x1700,
+};
+
+enum bitland_mifs_return_code {
+ WMI_RETURN_CODE_SUCCESS = 0x8000,
+ WMI_RETURN_CODE_UNKNOWN_FUNCTION = 0xE000,
};
enum bitland_system_ac_mode {
@@ -114,18 +120,14 @@ enum bitland_wmi_device_type {
};
struct bitland_mifs_input {
- u8 reserved1;
- u8 operation;
- u8 reserved2;
- u8 function;
+ __le16 operation;
+ __le16 function;
u8 payload[28];
} __packed;
struct bitland_mifs_output {
- u8 reserved1;
- u8 operation;
- u8 reserved2;
- u8 function;
+ __le16 return_code;
+ __le16 function;
u8 data[28];
} __packed;
@@ -171,15 +173,27 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
guard(mutex)(&data->lock);
- if (!output)
- return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf);
-
- ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output));
+ ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf,
+ sizeof(struct bitland_mifs_output));
if (ret)
return ret;
- memcpy(output, out_buf.data, sizeof(*output));
- kfree(out_buf.data);
+ struct bitland_mifs_output *result __free(kfree) = out_buf.data;
+
+ switch (le16_to_cpu(result->return_code)) {
+ case WMI_RETURN_CODE_SUCCESS:
+ break;
+ case WMI_RETURN_CODE_UNKNOWN_FUNCTION:
+ return -EOPNOTSUPP;
+ default:
+ return -EIO;
+ }
+
+ if (result->function != input->function)
+ return -ENOMSG;
+
+ if (output)
+ memcpy(output, result, sizeof(*output));
return 0;
}
@@ -189,10 +203,8 @@ static int laptop_profile_get(struct device *dev,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_GET,
- .reserved2 = 0,
- .function = WMI_FN_SYSTEM_PER_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
+ .function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE),
};
struct bitland_mifs_output result;
int ret;
@@ -223,8 +235,8 @@ static int laptop_profile_get(struct device *dev,
static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data)
{
struct bitland_mifs_input input = {
- .operation = WMI_METHOD_GET,
- .function = WMI_FN_SYSTEM_AC_TYPE,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
+ .function = cpu_to_le16(WMI_FN_SYSTEM_AC_TYPE),
};
struct bitland_mifs_output output;
int ret;
@@ -234,7 +246,11 @@ static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *da
return -EOPNOTSUPP;
ret = bitland_mifs_wmi_call(data, &input, &output);
- if (ret)
+ /* Not all systems support this function, do not perform further checks on them */
+ if (ret == -EOPNOTSUPP)
+ return 0;
+
+ if (ret < 0)
return ret;
if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE)
@@ -248,10 +264,8 @@ static int laptop_profile_set(struct device *dev,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_SET,
- .reserved2 = 0,
- .function = WMI_FN_SYSTEM_PER_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_SET),
+ .function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE),
};
int ret;
u8 val;
@@ -344,22 +358,20 @@ static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_GET,
- .reserved2 = 0,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
};
struct bitland_mifs_output res;
int ret;
switch (type) {
case hwmon_temp:
- input.function = WMI_FN_CPU_THERMOMETER;
+ input.function = cpu_to_le16(WMI_FN_CPU_THERMOMETER);
ret = bitland_mifs_wmi_call(data, &input, &res);
if (!ret)
*val = res.data[0] * MILLIDEGREE_PER_DEGREE;
return ret;
case hwmon_fan:
- input.function = WMI_FN_FAN_SPEEDS;
+ input.function = cpu_to_le16(WMI_FN_FAN_SPEEDS);
ret = bitland_mifs_wmi_call(data, &input, &res);
if (ret)
return ret;
@@ -420,10 +432,8 @@ static int laptop_kbd_led_set(struct led_classdev *led_cdev,
struct bitland_mifs_wmi_data *data =
container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_SET,
- .reserved2 = 0,
- .function = WMI_FN_RGB_KB_BRIGHTNESS,
+ .operation = cpu_to_le16(WMI_METHOD_SET),
+ .function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS),
};
input.payload[0] = (u8)value;
@@ -436,10 +446,8 @@ static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev)
struct bitland_mifs_wmi_data *data =
container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_GET,
- .reserved2 = 0,
- .function = WMI_FN_RGB_KB_BRIGHTNESS,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
+ .function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS),
};
struct bitland_mifs_output res;
int ret;
@@ -463,10 +471,8 @@ static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_GET,
- .reserved2 = 0,
- .function = WMI_FN_GPU_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
+ .function = cpu_to_le16(WMI_FN_GPU_MODE),
};
struct bitland_mifs_output res;
u8 mode_val;
@@ -488,10 +494,8 @@ static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_SET,
- .reserved2 = 0,
- .function = WMI_FN_GPU_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_SET),
+ .function = cpu_to_le16(WMI_FN_GPU_MODE),
};
int val;
int ret;
@@ -521,10 +525,8 @@ static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_GET,
- .reserved2 = 0,
- .function = WMI_FN_RGB_KB_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_GET),
+ .function = cpu_to_le16(WMI_FN_RGB_KB_MODE),
};
struct bitland_mifs_output res;
u8 mode_val;
@@ -546,10 +548,8 @@ static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_SET,
- .reserved2 = 0,
- .function = WMI_FN_RGB_KB_MODE,
+ .operation = cpu_to_le16(WMI_METHOD_SET),
+ .function = cpu_to_le16(WMI_FN_RGB_KB_MODE),
};
// the wmi value (0, 1, 2 or 3)
int val;
@@ -575,10 +575,8 @@ static ssize_t fan_boost_store(struct device *dev,
{
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
struct bitland_mifs_input input = {
- .reserved1 = 0,
- .operation = WMI_METHOD_SET,
- .reserved2 = 0,
- .function = WMI_FN_MAX_FAN_SWITCH,
+ .operation = cpu_to_le16(WMI_METHOD_SET),
+ .function = cpu_to_le16(WMI_FN_MAX_FAN_SWITCH),
};
bool val;
int ret;
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index d1b4df91401b..2b0ffcb56a95 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -122,6 +122,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
.driver_data = &g_series_quirks,
},
{
+ .ident = "Alienware Area-51 AAT2250",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51 AAT2250"),
+ },
+ .driver_data = &generic_quirks,
+ },
+ {
.ident = "Alienware Area-51m",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
diff --git a/drivers/platform/x86/dell/dell-dw5826e-reset.c b/drivers/platform/x86/dell/dell-dw5826e-reset.c
index 64e6e9b1d7a0..c72c243b460e 100644
--- a/drivers/platform/x86/dell/dell-dw5826e-reset.c
+++ b/drivers/platform/x86/dell/dell-dw5826e-reset.c
@@ -6,8 +6,10 @@
*/
#include <linux/acpi.h>
+#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/device.h>
+#include <linux/gfp_types.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
@@ -15,15 +17,23 @@
#define PALC_DSM_FN_TRIGGER_PLDR 1
+static const u64 palc_dsm_revs[] = { 1, 0 };
+
static guid_t palc_dsm_guid =
GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59);
+struct palc_data {
+ u64 dsm_rev;
+};
+
static int trigger_palc_pldr(struct device *dev, acpi_handle handle)
{
+ struct palc_data *data = dev_get_drvdata(dev);
union acpi_object *obj;
int ret = 0;
- obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR, NULL);
+ obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, data->dsm_rev,
+ PALC_DSM_FN_TRIGGER_PLDR, NULL);
if (!obj) {
dev_err(dev, "Failed to evaluate _DSM\n");
return -EIO;
@@ -60,16 +70,28 @@ ATTRIBUTE_GROUPS(palc);
static int palc_probe(struct platform_device *pdev)
{
+ struct palc_data *data;
acpi_handle handle;
+ unsigned int i;
handle = ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
- if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, BIT(PALC_DSM_FN_TRIGGER_PLDR)))
- return -ENODEV;
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(palc_dsm_revs); i++) {
+ if (acpi_check_dsm(handle, &palc_dsm_guid, palc_dsm_revs[i],
+ BIT(PALC_DSM_FN_TRIGGER_PLDR))) {
+ data->dsm_rev = palc_dsm_revs[i];
+ platform_set_drvdata(pdev, data);
+ return 0;
+ }
+ }
- return 0;
+ return -ENODEV;
}
static const struct acpi_device_id palc_acpi_ids[] = {
diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c
index 54096495719b..42c0032991d2 100644
--- a/drivers/platform/x86/dell/dell-wmi-aio.c
+++ b/drivers/platform/x86/dell/dell-wmi-aio.c
@@ -5,14 +5,20 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
-#include <linux/acpi.h>
#include <linux/string.h>
+#include <linux/wmi.h>
MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series");
MODULE_LICENSE("GPL");
@@ -20,22 +26,22 @@ MODULE_LICENSE("GPL");
#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
-struct dell_wmi_event {
- u16 length;
- /* 0x000: A hot key pressed or an event occurred
- * 0x00F: A sequence of hot keys are pressed */
- u16 type;
- u16 event[];
+struct dell_wmi_aio_data {
+ struct input_dev *input_device;
+ /* Protects the input sequence */
+ struct mutex input_lock;
};
-static const char *dell_wmi_aio_guids[] = {
- EVENT_GUID1,
- EVENT_GUID2,
- NULL
-};
+/* A hot key was pressed or an event occurred */
+#define DELL_AIO_EVENT_TYPE_SINGLE 0x0000
+/* A sequence of hot keys was pressed */
+#define DELL_AIO_EVENT_TYPE_SEQUENCE 0x000F
-MODULE_ALIAS("wmi:"EVENT_GUID1);
-MODULE_ALIAS("wmi:"EVENT_GUID2);
+struct dell_wmi_event {
+ __le16 length;
+ __le16 type;
+ __le16 event[];
+} __packed;
static const struct key_entry dell_wmi_aio_keymap[] = {
{ KE_KEY, 0xc0, { KEY_VOLUMEUP } },
@@ -50,137 +56,96 @@ static const struct key_entry dell_wmi_aio_keymap[] = {
{ KE_END, 0 }
};
-static struct input_dev *dell_wmi_aio_input_dev;
-
/*
* The new WMI event data format will follow the dell_wmi_event structure
* So, we will check if the buffer matches the format
*/
-static bool dell_wmi_aio_event_check(u8 *buffer, int length)
+static bool dell_wmi_aio_event_check(const struct wmi_buffer *buffer)
{
- struct dell_wmi_event *event = (struct dell_wmi_event *)buffer;
+ struct dell_wmi_event *event;
+ u16 length, type;
- if (event == NULL || length < 6)
+ if (buffer->length < struct_size(event, event, 1))
return false;
- if ((event->type == 0 || event->type == 0xf) &&
- event->length >= 2)
- return true;
+ event = buffer->data;
+ length = le16_to_cpu(event->length);
+ type = le16_to_cpu(event->type);
- return false;
-}
+ if (type != DELL_AIO_EVENT_TYPE_SINGLE && type != DELL_AIO_EVENT_TYPE_SEQUENCE)
+ return false;
-static void dell_wmi_aio_notify(union acpi_object *obj, void *context)
-{
- struct dell_wmi_event *event;
+ if (length < 2)
+ return false;
- if (obj) {
- unsigned int scancode = 0;
-
- switch (obj->type) {
- case ACPI_TYPE_INTEGER:
- /* Most All-In-One correctly return integer scancode */
- scancode = obj->integer.value;
- sparse_keymap_report_event(dell_wmi_aio_input_dev,
- scancode, 1, true);
- break;
- case ACPI_TYPE_BUFFER:
- if (dell_wmi_aio_event_check(obj->buffer.pointer,
- obj->buffer.length)) {
- event = (struct dell_wmi_event *)
- obj->buffer.pointer;
- scancode = event->event[0];
- } else {
- /* Broken machines return the scancode in a
- buffer */
- if (obj->buffer.pointer &&
- obj->buffer.length > 0)
- scancode = obj->buffer.pointer[0];
- }
- if (scancode)
- sparse_keymap_report_event(
- dell_wmi_aio_input_dev,
- scancode, 1, true);
- break;
- }
- }
+ return true;
}
-static int __init dell_wmi_aio_input_setup(void)
+static void dell_wmi_aio_notify(struct wmi_device *wdev, const struct wmi_buffer *data)
{
- int err;
-
- dell_wmi_aio_input_dev = input_allocate_device();
-
- if (!dell_wmi_aio_input_dev)
- return -ENOMEM;
-
- dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys";
- dell_wmi_aio_input_dev->phys = "wmi/input0";
- dell_wmi_aio_input_dev->id.bustype = BUS_HOST;
-
- err = sparse_keymap_setup(dell_wmi_aio_input_dev,
- dell_wmi_aio_keymap, NULL);
- if (err) {
- pr_err("Unable to setup input device keymap\n");
- goto err_free_dev;
- }
- err = input_register_device(dell_wmi_aio_input_dev);
- if (err) {
- pr_info("Unable to register input device\n");
- goto err_free_dev;
+ struct dell_wmi_aio_data *drvdata = dev_get_drvdata(&wdev->dev);
+ const struct dell_wmi_event *new_event;
+ unsigned int scancode;
+ const u8 *old_event;
+
+ if (dell_wmi_aio_event_check(data)) {
+ new_event = data->data;
+ scancode = le16_to_cpu(new_event->event[0]);
+ } else {
+ old_event = data->data;
+ scancode = old_event[0];
}
- return 0;
-err_free_dev:
- input_free_device(dell_wmi_aio_input_dev);
- return err;
-}
+ guard(mutex)(&drvdata->input_lock);
-static const char *dell_wmi_aio_find(void)
-{
- int i;
-
- for (i = 0; dell_wmi_aio_guids[i] != NULL; i++)
- if (wmi_has_guid(dell_wmi_aio_guids[i]))
- return dell_wmi_aio_guids[i];
-
- return NULL;
+ sparse_keymap_report_event(drvdata->input_device, scancode, 1, true);
}
-static int __init dell_wmi_aio_init(void)
+static int dell_wmi_aio_probe(struct wmi_device *wdev, const void *context)
{
- int err;
- const char *guid;
+ struct dell_wmi_aio_data *data;
+ int ret;
- guid = dell_wmi_aio_find();
- if (!guid) {
- pr_warn("No known WMI GUID found\n");
- return -ENXIO;
- }
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
- err = dell_wmi_aio_input_setup();
- if (err)
- return err;
+ dev_set_drvdata(&wdev->dev, data);
+ ret = devm_mutex_init(&wdev->dev, &data->input_lock);
+ if (ret < 0)
+ return ret;
- err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
- if (err) {
- pr_err("Unable to register notify handler - %d\n", err);
- input_unregister_device(dell_wmi_aio_input_dev);
- return err;
- }
+ data->input_device = devm_input_allocate_device(&wdev->dev);
+ if (!data->input_device)
+ return -ENOMEM;
- return 0;
-}
+ data->input_device->name = "Dell AIO WMI hotkeys";
+ data->input_device->phys = "wmi/input0";
+ data->input_device->id.bustype = BUS_HOST;
-static void __exit dell_wmi_aio_exit(void)
-{
- const char *guid;
+ ret = sparse_keymap_setup(data->input_device, dell_wmi_aio_keymap, NULL);
+ if (ret < 0)
+ return ret;
- guid = dell_wmi_aio_find();
- wmi_remove_notify_handler(guid);
- input_unregister_device(dell_wmi_aio_input_dev);
+ return input_register_device(data->input_device);
}
-module_init(dell_wmi_aio_init);
-module_exit(dell_wmi_aio_exit);
+static const struct wmi_device_id dell_wmi_aio_id_table[] = {
+ { EVENT_GUID1, NULL },
+ { EVENT_GUID2, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, dell_wmi_aio_id_table);
+
+static struct wmi_driver dell_wmi_aio_driver = {
+ .driver = {
+ .name = "dell-wmi-aio",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = dell_wmi_aio_id_table,
+ .probe = dell_wmi_aio_probe,
+ .notify_new = dell_wmi_aio_notify,
+ .min_event_size = sizeof(u8),
+ .no_singleton = true,
+};
+module_wmi_driver(dell_wmi_aio_driver);
diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c
index c9236738f896..ced93795c5d4 100644
--- a/drivers/platform/x86/gpd-pocket-fan.c
+++ b/drivers/platform/x86/gpd-pocket-fan.c
@@ -153,10 +153,6 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)
return -ENOMEM;
fan->dev = &pdev->dev;
- ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work,
- gpd_pocket_fan_worker);
- if (ret)
- return ret;
/* Note this returns a "weak" reference which we don't need to free */
fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0");
@@ -175,6 +171,11 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)
if (IS_ERR(fan->gpio1))
return PTR_ERR(fan->gpio1);
+ ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work,
+ gpd_pocket_fan_worker);
+ if (ret)
+ return ret;
+
gpd_pocket_fan_force_update(fan);
platform_set_drvdata(pdev, fan);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
index d5a2d0963bbf..8b24e52653d0 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -12,7 +12,7 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
-#include <linux/string.h>
+#include <linux/string_helpers.h>
#include <linux/wmi.h>
#include "bioscfg.h"
#include <linux/nls.h>
@@ -53,74 +53,44 @@ int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer)
int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
{
+ char utf8_buf[MAX_BUFF_SIZE];
u16 *src = (u16 *)*buffer;
+ int escaped_len;
+ u16 orig_size;
u16 src_size;
-
- u16 size;
- int i;
- int conv_dst_size;
+ int utf8_len;
if (*buffer_size < sizeof(u16))
return -EINVAL;
- src_size = *(src++);
- /* size value in u16 chars */
- size = src_size / sizeof(u16);
+ src_size = *src;
- /* Ensure there is enough space remaining to read and convert
- * the string
+ /*
+ * Ensure there is enough space remaining for the length prefix
+ * just read plus the string data it describes.
*/
- if (*buffer_size < src_size)
+ if (*buffer_size < sizeof(u16) + src_size)
return -EINVAL;
- for (i = 0; i < size; i++)
- if (src[i] == '\\' ||
- src[i] == '\r' ||
- src[i] == '\n' ||
- src[i] == '\t')
- size++;
+ src++;
+ /* size value in u16 chars */
+ orig_size = src_size / sizeof(u16);
- /*
- * Conversion is limited to destination string max number of
- * bytes.
- */
- conv_dst_size = size;
- if (size >= dst_size)
- conv_dst_size = dst_size - 1;
+ utf8_len = utf16s_to_utf8s(src, orig_size, UTF16_HOST_ENDIAN,
+ utf8_buf, sizeof(utf8_buf));
- /*
- * convert from UTF-16 unicode to ASCII
- */
- utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size);
- dst[conv_dst_size] = 0;
-
- for (i = 0; i < conv_dst_size; i++) {
- if (*src == '\\' ||
- *src == '\r' ||
- *src == '\n' ||
- *src == '\t') {
- dst[i++] = '\\';
- if (i == conv_dst_size)
- break;
- }
+ escaped_len = string_escape_mem(utf8_buf, utf8_len, dst, dst_size - 1,
+ ESCAPE_SPACE | ESCAPE_SPECIAL,
+ "\\\r\n\t");
+ if (escaped_len > dst_size - 1)
+ escaped_len = dst_size - 1;
+ dst[escaped_len] = '\0';
+ strreplace(dst, '"', '\'');
- if (*src == '\r')
- dst[i] = 'r';
- else if (*src == '\n')
- dst[i] = 'n';
- else if (*src == '\t')
- dst[i] = 't';
- else if (*src == '"')
- dst[i] = '\'';
- else
- dst[i] = *src;
- src++;
- }
-
- *buffer = (u8 *)src;
- *buffer_size -= size * sizeof(u16);
+ *buffer += sizeof(u16) + src_size;
+ *buffer_size -= sizeof(u16) + src_size;
- return size;
+ return escaped_len;
}
int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size,
@@ -442,7 +412,7 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *
*len = 0;
*str = NULL;
- new_str = kmalloc(input_len, GFP_KERNEL);
+ new_str = kzalloc(2 * DIV_ROUND_UP(input_len, 5) + 1, GFP_KERNEL);
if (!new_str)
return -ENOMEM;
@@ -484,6 +454,103 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *
return ret;
}
+/**
+ * hp_get_common_data_from_package() - Parse the package elements shared
+ * by every attribute type (PATH..SECURITY_LEVEL)
+ * @obj: ACPI object package for this attribute instance
+ * @obj_count: Number of elements in @obj
+ * @elem: Current position in @obj; advanced in place when PREREQUISITES
+ * consumes additional array elements
+ * @eloc: Current common-element locator; advanced in place when
+ * PREREQUISITES_SIZE is zero, since PREREQUISITES is then omitted
+ * by BIOS
+ * @int_value: Integer value already decoded by the caller for *@eloc,
+ * when applicable
+ * @str_value: String value already decoded by the caller for *@eloc, when
+ * applicable; reused and reset to NULL while walking
+ * PREREQUISITES, same as the caller does with its own copy
+ * @common: Destination common_data struct for this attribute instance
+ *
+ * Called by each type's own hp_populate_*_elements_from_package()
+ * instead of duplicating this parsing per type, since every BIOS
+ * attribute package uses the same layout for these elements (see
+ * struct common_data).
+ *
+ * Return: 0 on success, or -EINVAL if @obj is too small for the
+ * declared PREREQUISITES_SIZE.
+ */
+int hp_get_common_data_from_package(union acpi_object *obj, int obj_count,
+ int *elem, int *eloc, u32 int_value,
+ char **str_value, struct common_data *common)
+{
+ int value_len;
+ int reqs;
+ int size;
+
+ switch (*eloc) {
+ case PATH:
+ strscpy(common->path, *str_value);
+ break;
+ case IS_READONLY:
+ common->is_readonly = int_value;
+ break;
+ case DISPLAY_IN_UI:
+ common->display_in_ui = int_value;
+ break;
+ case REQUIRES_PHYSICAL_PRESENCE:
+ common->requires_physical_presence = int_value;
+ break;
+ case SEQUENCE:
+ common->sequence = int_value;
+ break;
+ case PREREQUISITES_SIZE:
+ if (int_value > MAX_PREREQUISITES_SIZE) {
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ int_value = MAX_PREREQUISITES_SIZE;
+ }
+ common->prerequisites_size = int_value;
+
+ /*
+ * This step is needed to keep the expected
+ * element list pointing to the right obj[elem].type
+ * when the size is zero. PREREQUISITES
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (common->prerequisites_size == 0)
+ (*eloc)++;
+ break;
+ case PREREQUISITES:
+ size = min_t(u32, common->prerequisites_size, MAX_PREREQUISITES_SIZE);
+
+ for (reqs = 0; reqs < size; reqs++) {
+ if (*elem + reqs >= obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ if (hp_convert_hexstr_to_str(obj[*elem + reqs].string.pointer,
+ obj[*elem + reqs].string.length,
+ str_value, &value_len))
+ continue;
+
+ strscpy(common->prerequisites[reqs], *str_value);
+ kfree(*str_value);
+ *str_value = NULL;
+ }
+ if (size)
+ *elem += size - 1;
+ break;
+ case SECURITY_LEVEL:
+ common->security_level = int_value;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
/* map output size to the corresponding WMI method id */
int hp_encode_outsize_for_pvsz(int outsize)
{
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
index ac57d6eab4c3..94619c034187 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
@@ -493,5 +493,8 @@ void hp_friendly_user_name_update(char *path, const char *attr_name,
char *attr_display, int attr_size);
int hp_wmi_error_and_message(int error_code);
int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, struct common_data *common);
+int hp_get_common_data_from_package(union acpi_object *obj, int obj_count,
+ int *elem, int *eloc, u32 int_value,
+ char **str_value, struct common_data *common);
#endif
diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
index 72a4c2107d7e..cc18cd7902de 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
@@ -132,7 +132,6 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
u32 size = 0;
u32 int_value = 0;
int elem = 0;
- int reqs;
int pos_values;
int ret;
int eloc;
@@ -176,64 +175,18 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
case VALUE:
break;
case PATH:
- strscpy(enum_data->common.path, str_value);
- break;
case IS_READONLY:
- enum_data->common.is_readonly = int_value;
- break;
case DISPLAY_IN_UI:
- enum_data->common.display_in_ui = int_value;
- break;
case REQUIRES_PHYSICAL_PRESENCE:
- enum_data->common.requires_physical_presence = int_value;
- break;
case SEQUENCE:
- enum_data->common.sequence = int_value;
- break;
case PREREQUISITES_SIZE:
- if (int_value > MAX_PREREQUISITES_SIZE) {
- pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
- int_value = MAX_PREREQUISITES_SIZE;
- }
- enum_data->common.prerequisites_size = int_value;
-
- /*
- * This step is needed to keep the expected
- * element list pointing to the right obj[elem].type
- * when the size is zero. PREREQUISITES
- * object is omitted by BIOS when the size is
- * zero.
- */
- if (int_value == 0)
- eloc++;
- break;
-
case PREREQUISITES:
- size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
- for (reqs = 0; reqs < size; reqs++) {
- if (elem + reqs >= enum_obj_count) {
- pr_err("Error enum-objects package is too small\n");
- return -EINVAL;
- }
-
- ret = hp_convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer,
- enum_obj[elem + reqs].string.length,
- &str_value, &value_len);
-
- if (ret)
- return -EINVAL;
-
- strscpy(enum_data->common.prerequisites[reqs], str_value);
-
- kfree(str_value);
- str_value = NULL;
- }
- if (size)
- elem += size - 1;
- break;
-
case SECURITY_LEVEL:
- enum_data->common.security_level = int_value;
+ ret = hp_get_common_data_from_package(enum_obj, enum_obj_count,
+ &elem, &eloc, int_value, &str_value,
+ &enum_data->common);
+ if (ret)
+ return ret;
break;
case ENUM_CURRENT_VALUE:
diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
index a27907066448..07654e805791 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -145,9 +145,7 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
int ret;
u32 int_value = 0;
int elem;
- int reqs;
int eloc;
- int size;
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
if (!integer_obj)
@@ -192,63 +190,18 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
integer_data->current_value = int_value;
break;
case PATH:
- strscpy(integer_data->common.path, str_value);
- break;
case IS_READONLY:
- integer_data->common.is_readonly = int_value;
- break;
case DISPLAY_IN_UI:
- integer_data->common.display_in_ui = int_value;
- break;
case REQUIRES_PHYSICAL_PRESENCE:
- integer_data->common.requires_physical_presence = int_value;
- break;
case SEQUENCE:
- integer_data->common.sequence = int_value;
- break;
case PREREQUISITES_SIZE:
- if (int_value > MAX_PREREQUISITES_SIZE) {
- pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
- int_value = MAX_PREREQUISITES_SIZE;
- }
- integer_data->common.prerequisites_size = int_value;
-
- /*
- * This step is needed to keep the expected
- * element list pointing to the right obj[elem].type
- * when the size is zero. PREREQUISITES
- * object is omitted by BIOS when the size is
- * zero.
- */
- if (integer_data->common.prerequisites_size == 0)
- eloc++;
- break;
case PREREQUISITES:
- size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
-
- for (reqs = 0; reqs < size; reqs++) {
- if (elem + reqs >= integer_obj_count) {
- pr_err("Error elem-objects package is too small\n");
- return -EINVAL;
- }
-
- ret = hp_convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
- integer_obj[elem + reqs].string.length,
- &str_value, &value_len);
-
- if (ret)
- continue;
-
- strscpy(integer_data->common.prerequisites[reqs], str_value);
- kfree(str_value);
- str_value = NULL;
- }
- if (size)
- elem += size - 1;
- break;
-
case SECURITY_LEVEL:
- integer_data->common.security_level = int_value;
+ ret = hp_get_common_data_from_package(integer_obj, integer_obj_count,
+ &elem, &eloc, int_value, &str_value,
+ &integer_data->common);
+ if (ret)
+ return ret;
break;
case INT_LOWER_BOUND:
integer_data->lower_bound = int_value;
@@ -331,7 +284,12 @@ static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_
// VALUE:
integer_data->current_value = 0;
- hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ if (ret < 0) {
+ kfree(dst);
+ return ret;
+ }
+
ret = kstrtoint(dst, 10, &integer_data->current_value);
if (ret)
pr_warn("Unable to convert string to integer: %s\n", dst);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
index 5bf8d40bdf81..ccb1ea03ef64 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
@@ -98,8 +98,13 @@ int hp_alloc_ordered_list_data(void)
{
bioscfg_drv.ordered_list_instances_count =
hp_get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID);
- bioscfg_drv.ordered_list_data = kzalloc_objs(*bioscfg_drv.ordered_list_data,
- bioscfg_drv.ordered_list_instances_count);
+
+ if (!bioscfg_drv.ordered_list_instances_count)
+ return -EINVAL;
+ bioscfg_drv.ordered_list_data =
+ kvzalloc_objs(*bioscfg_drv.ordered_list_data,
+ bioscfg_drv.ordered_list_instances_count);
+
if (!bioscfg_drv.ordered_list_data) {
bioscfg_drv.ordered_list_instances_count = 0;
return -ENOMEM;
@@ -130,11 +135,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
char *str_value = NULL;
int value_len = 0;
int ret;
- u32 size;
u32 int_value = 0;
int elem;
int olist_elem;
- int reqs;
int eloc;
char *tmpstr = NULL;
char *part_tmp = NULL;
@@ -180,64 +183,18 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
break;
case PATH:
- strscpy(ordered_list_data->common.path, str_value);
- break;
case IS_READONLY:
- ordered_list_data->common.is_readonly = int_value;
- break;
case DISPLAY_IN_UI:
- ordered_list_data->common.display_in_ui = int_value;
- break;
case REQUIRES_PHYSICAL_PRESENCE:
- ordered_list_data->common.requires_physical_presence = int_value;
- break;
case SEQUENCE:
- ordered_list_data->common.sequence = int_value;
- break;
case PREREQUISITES_SIZE:
- if (int_value > MAX_PREREQUISITES_SIZE) {
- pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
- int_value = MAX_PREREQUISITES_SIZE;
- }
- ordered_list_data->common.prerequisites_size = int_value;
-
- /*
- * This step is needed to keep the expected
- * element list pointing to the right obj[elem].type
- * when the size is zero. PREREQUISITES
- * object is omitted by BIOS when the size is
- * zero.
- */
- if (int_value == 0)
- eloc++;
- break;
case PREREQUISITES:
- size = min_t(u32, ordered_list_data->common.prerequisites_size,
- MAX_PREREQUISITES_SIZE);
- for (reqs = 0; reqs < size; reqs++) {
- if (elem + reqs >= order_obj_count) {
- pr_err("Error elem-objects package is too small\n");
- return -EINVAL;
- }
-
- ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
- order_obj[elem + reqs].string.length,
- &str_value, &value_len);
-
- if (ret)
- continue;
-
- strscpy(ordered_list_data->common.prerequisites[reqs], str_value);
-
- kfree(str_value);
- str_value = NULL;
- }
- if (size)
- elem += size - 1;
- break;
-
case SECURITY_LEVEL:
- ordered_list_data->common.security_level = int_value;
+ ret = hp_get_common_data_from_package(order_obj, order_obj_count,
+ &elem, &eloc, int_value, &str_value,
+ &ordered_list_data->common);
+ if (ret)
+ return ret;
break;
case ORD_LIST_SIZE:
@@ -438,6 +395,6 @@ void hp_exit_ordered_list_attributes(void)
}
bioscfg_drv.ordered_list_instances_count = 0;
- kfree(bioscfg_drv.ordered_list_data);
+ kvfree(bioscfg_drv.ordered_list_data);
bioscfg_drv.ordered_list_data = NULL;
}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
index a9e178637416..e7921d9295e0 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -60,7 +60,8 @@ int hp_get_password_instance_for_type(const char *name)
return -EINVAL;
}
-static int validate_password_input(int instance_id, const char *buf)
+static int validate_password_input(int instance_id, const char *buf,
+ bool is_current)
{
int length;
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
@@ -69,13 +70,18 @@ static int validate_password_input(int instance_id, const char *buf)
if (length > 0 && buf[length - 1] == '\n')
length--;
- if (length > MAX_PASSWD_SIZE)
- return INVALID_BIOS_AUTH;
+ /* An empty current_password clears the session password */
+ if (is_current && !length)
+ return 0;
+
+ if (length >= MAX_PASSWD_SIZE)
+ return -E2BIG;
if (password_data->min_password_length > length ||
password_data->max_password_length < length)
- return INVALID_BIOS_AUTH;
- return SUCCESS;
+ return -E2BIG;
+
+ return 0;
}
ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
@@ -96,7 +102,7 @@ static int store_password_instance(struct kobject *kobj, const char *buf,
id = get_password_instance_id(kobj);
if (id >= 0)
- ret = validate_password_input(id, buf_cp);
+ ret = validate_password_input(id, buf_cp, is_current);
}
if (!ret) {
@@ -224,7 +230,6 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
u32 size;
u32 int_value = 0;
int elem;
- int reqs;
int eloc;
int pos_values;
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
@@ -268,64 +273,18 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
case VALUE:
break;
case PATH:
- strscpy(password_data->common.path, str_value);
- break;
case IS_READONLY:
- password_data->common.is_readonly = int_value;
- break;
case DISPLAY_IN_UI:
- password_data->common.display_in_ui = int_value;
- break;
case REQUIRES_PHYSICAL_PRESENCE:
- password_data->common.requires_physical_presence = int_value;
- break;
case SEQUENCE:
- password_data->common.sequence = int_value;
- break;
case PREREQUISITES_SIZE:
- if (int_value > MAX_PREREQUISITES_SIZE) {
- pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
- int_value = MAX_PREREQUISITES_SIZE;
- }
- password_data->common.prerequisites_size = int_value;
-
- /* This step is needed to keep the expected
- * element list pointing to the right obj[elem].type
- * when the size is zero. PREREQUISITES
- * object is omitted by BIOS when the size is
- * zero.
- */
- if (int_value == 0)
- eloc++;
- break;
case PREREQUISITES:
- size = min_t(u32, password_data->common.prerequisites_size,
- MAX_PREREQUISITES_SIZE);
-
- for (reqs = 0; reqs < size; reqs++) {
- if (elem + reqs >= password_obj_count) {
- pr_err("Error elem-objects package is too small\n");
- return -EINVAL;
- }
-
- ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
- password_obj[elem + reqs].string.length,
- &str_value, &value_len);
-
- if (ret)
- break;
-
- strscpy(password_data->common.prerequisites[reqs], str_value);
-
- kfree(str_value);
- str_value = NULL;
-
- }
- if (size)
- elem += size - 1;
- break;
case SECURITY_LEVEL:
- password_data->common.security_level = int_value;
+ ret = hp_get_common_data_from_package(password_obj, password_obj_count,
+ &elem, &eloc, int_value, &str_value,
+ &password_data->common);
+ if (ret)
+ return ret;
break;
case PSWD_MIN_LENGTH:
password_data->min_password_length = int_value;
diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
index 4d94e48c1a4c..343b2356943e 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
@@ -6,6 +6,7 @@
* Copyright (c) 2022 HP Development Company, L.P.
*/
+#include <linux/array_size.h>
#include "bioscfg.h"
static const char * const spm_state_types[] = {
@@ -47,9 +48,6 @@ size_t hp_calculate_security_buffer(const char *authentication)
return sizeof(u16) * 2;
authlen = strlen(authentication);
- if (!authlen)
- return sizeof(u16) * 2;
-
size = sizeof(u16) + authlen * sizeof(u16);
if (!strstarts(authentication, BEAM_PREFIX))
size += strlen(UTF_PREFIX) * sizeof(u16);
@@ -118,6 +116,9 @@ static ssize_t update_spm_state(void)
if (ret < 0)
return ret;
+ if (data.state >= ARRAY_SIZE(spm_mechanism_types))
+ return -EIO;
+
bioscfg_drv.spm_data.mechanism = data.state;
if (bioscfg_drv.spm_data.mechanism)
bioscfg_drv.spm_data.is_enabled = 1;
@@ -153,6 +154,9 @@ static ssize_t status_show(struct kobject *kobj, struct kobj_attribute
if (ret < 0)
return ret;
+ if (data.state >= ARRAY_SIZE(spm_state_types))
+ return -EIO;
+
/*
* 'status' is a read-only file that returns ASCII text in
* JSON format reporting the status information.
@@ -310,7 +314,7 @@ static ssize_t auth_token_store(struct kobject *kobj,
length--;
/* allocate space and copy current auth token */
- bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL);
+ bioscfg_drv.spm_data.auth_token = kstrndup(buf, length, GFP_KERNEL);
if (!bioscfg_drv.spm_data.auth_token) {
ret = -ENOMEM;
goto exit_token;
diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
index f3dfba270f7e..c433b67870bd 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -101,8 +101,13 @@ static const struct attribute_group string_attr_group = {
int hp_alloc_string_data(void)
{
bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID);
- bioscfg_drv.string_data = kzalloc_objs(*bioscfg_drv.string_data,
- bioscfg_drv.string_instances_count);
+
+ if (!bioscfg_drv.string_instances_count)
+ return -EINVAL;
+ bioscfg_drv.string_data =
+ kvzalloc_objs(*bioscfg_drv.string_data,
+ bioscfg_drv.string_instances_count);
+
if (!bioscfg_drv.string_data) {
bioscfg_drv.string_instances_count = 0;
return -ENOMEM;
@@ -135,9 +140,7 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
int ret = 0;
u32 int_value = 0;
int elem;
- int reqs;
int eloc;
- int size;
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
if (!string_obj)
@@ -181,64 +184,18 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
strscpy(string_data->current_value, str_value);
break;
case PATH:
- strscpy(string_data->common.path, str_value);
- break;
case IS_READONLY:
- string_data->common.is_readonly = int_value;
- break;
case DISPLAY_IN_UI:
- string_data->common.display_in_ui = int_value;
- break;
case REQUIRES_PHYSICAL_PRESENCE:
- string_data->common.requires_physical_presence = int_value;
- break;
case SEQUENCE:
- string_data->common.sequence = int_value;
- break;
case PREREQUISITES_SIZE:
- if (int_value > MAX_PREREQUISITES_SIZE) {
- pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
- int_value = MAX_PREREQUISITES_SIZE;
- }
- string_data->common.prerequisites_size = int_value;
-
- /*
- * This step is needed to keep the expected
- * element list pointing to the right obj[elem].type
- * when the size is zero. PREREQUISITES
- * object is omitted by BIOS when the size is
- * zero.
- */
- if (string_data->common.prerequisites_size == 0)
- eloc++;
- break;
case PREREQUISITES:
- size = min_t(u32, string_data->common.prerequisites_size,
- MAX_PREREQUISITES_SIZE);
-
- for (reqs = 0; reqs < size; reqs++) {
- if (elem + reqs >= string_obj_count) {
- pr_err("Error elem-objects package is too small\n");
- return -EINVAL;
- }
-
- ret = hp_convert_hexstr_to_str(string_obj[elem + reqs].string.pointer,
- string_obj[elem + reqs].string.length,
- &str_value, &value_len);
-
- if (ret)
- continue;
-
- strscpy(string_data->common.prerequisites[reqs], str_value);
- kfree(str_value);
- str_value = NULL;
- }
- if (size)
- elem += size - 1;
- break;
-
case SECURITY_LEVEL:
- string_data->common.security_level = int_value;
+ ret = hp_get_common_data_from_package(string_obj, string_obj_count,
+ &elem, &eloc, int_value, &str_value,
+ &string_data->common);
+ if (ret)
+ return ret;
break;
case STR_MIN_LENGTH:
string_data->min_length = int_value;
@@ -390,6 +347,6 @@ void hp_exit_string_attributes(void)
}
bioscfg_drv.string_instances_count = 0;
- kfree(bioscfg_drv.string_data);
+ kvfree(bioscfg_drv.string_data);
bioscfg_drv.string_data = NULL;
}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
index b57e42f29282..d689ee844be2 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
@@ -90,17 +90,15 @@ static ssize_t audit_log_entries_show(struct kobject *kobj,
HPWMI_SURESTART,
audit_log_buffer, 1, 128);
- if (ret < 0 || (LOG_ENTRY_SIZE * i) > PAGE_SIZE) {
- /*
- * Encountered a failure while reading
- * individual logs. Only a partial list of
- * audit log will be returned.
- */
+ /*
+ * Encountered a failure while reading individual logs.
+ * Only a partial list of audit log will be returned.
+ */
+ if (ret < 0)
break;
- } else {
- memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
- buf += LOG_ENTRY_SIZE;
- }
+
+ memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
+ buf += LOG_ENTRY_SIZE;
}
return i * LOG_ENTRY_SIZE;
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index 615b4cf6fc45..52077833ac89 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -15,9 +15,11 @@
#include <linux/acpi.h>
#include <linux/array_size.h>
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
+#include <linux/devm-helpers.h>
#include <linux/dmi.h>
#include <linux/fixp-arith.h>
#include <linux/hwmon.h>
@@ -39,6 +41,8 @@
#include <linux/types.h>
#include <linux/workqueue.h>
+#include <acpi/battery.h>
+
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
MODULE_DESCRIPTION("HP laptop WMI driver");
MODULE_LICENSE("GPL");
@@ -207,7 +211,7 @@ static const char * const omen_thermal_profile_boards[] = {
"886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6",
"88F7", "88FD", "88FE", "88FF",
"8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB",
- "8A15", "8A42", "8A43",
+ "8A15", "8A17", "8A18", "8A42", "8A43",
"8BAD",
"8C58",
"8E41",
@@ -227,14 +231,16 @@ static const char * const omen_thermal_profile_force_v0_boards[] = {
* "balanced" when reaching zero.
*/
static const char * const omen_timed_thermal_profile_boards[] = {
- "8A15", "8A42",
+ "8A15", "8A17", "8A42",
"8BAD",
};
-/* DMI Board names of Victus 16-d laptops */
+/* DMI Board names of Victus 16-d and 16-e laptops */
static const char * const victus_thermal_profile_boards[] = {
+ "88EE",
"88F8",
"8A25",
+ "8A26",
};
/* DMI board-specific feature data for Omen and Victus laptops. */
@@ -324,10 +330,22 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.driver_data = (void *)&omen_v1_legacy_board_params,
},
{
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D3F") },
+ .driver_data = (void *)&omen_v1_legacy_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D40") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
+ {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
.driver_data = (void *)&omen_v1_no_ec_board_params,
},
{
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D42") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
+ {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") },
.driver_data = (void *)&omen_v1_no_ec_board_params,
},
@@ -336,13 +354,29 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.driver_data = (void *)&omen_v1_no_ec_board_params,
},
{
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD0") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
+ {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") },
.driver_data = (void *)&omen_v1_no_ec_board_params,
},
{
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DE1") },
+ .driver_data = (void *)&victus_s_board_params,
+ },
+ {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
.driver_data = (void *)&omen_v1_legacy_board_params,
},
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E41") },
+ .driver_data = (void *)&omen_v1_legacy_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8F1C") },
+ .driver_data = (void *)&omen_v1_legacy_board_params,
+ },
{},
};
@@ -404,6 +438,7 @@ enum hp_wmi_commandtype {
HPWMI_FEATURE2_QUERY = 0x0d,
HPWMI_WIRELESS2_QUERY = 0x1b,
HPWMI_POSTCODEERROR_QUERY = 0x2a,
+ HPWMI_BATTERY_CHARGE_MODE = 0x2b,
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
HPWMI_GRAPHICS_MUX_QUERY = 0x52,
@@ -473,6 +508,30 @@ enum hp_wireless2_bits {
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
+#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
+#define HPWMI_CHARGE_BEHAVIOURS \
+ (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
+
+/*
+ * SBCO reads the requested mode from bits 15:8 of the input value.
+ * GBCO returns literal status values in bits 7:0.
+ */
+#define HPWMI_CHARGE_MODE_MASK GENMASK(15, 8)
+#define HPWMI_CHARGE_MODE_AUTO 0x00
+#define HPWMI_CHARGE_MODE_FORCE_DISCHARGE 0x02
+#define HPWMI_CHARGE_MODE_INHIBIT 0x05
+
+#define HPWMI_CHARGE_MODE_READ_MASK GENMASK(7, 0)
+#define HPWMI_CHARGE_MODE_READ_AUTO 0x00
+#define HPWMI_CHARGE_MODE_READ_CHARGING 0x01
+#define HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE 0x02
+#define HPWMI_CHARGE_MODE_READ_INHIBIT 0x04
+#define HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM 0x05
+#define HPWMI_CHARGE_MODE_READ_INHIBIT_EXT 0x06
+#endif
+
struct bios_rfkill2_device_state {
u8 radio_type;
u8 bus_type;
@@ -533,7 +592,6 @@ static struct platform_device *hp_wmi_platform_dev;
static struct device *platform_profile_device;
static struct notifier_block platform_power_source_nb;
static enum platform_profile_option active_platform_profile;
-static bool platform_profile_support;
static bool zero_insize_support;
static struct rfkill *wifi_rfkill;
@@ -560,6 +618,26 @@ static const char * const tablet_chassis_types[] = {
"32" /* Detachable */
};
+#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
+static const struct dmi_system_id hp_wmi_charge_control_quirks[] __initconst = {
+ {
+ /* HP Spectre x360 16-aa0xxx */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
+ DMI_MATCH(DMI_BOARD_NAME, "8C17"),
+ },
+ },
+ {
+ /* HP Spectre x360 14-eu0xxx */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
+ DMI_MATCH(DMI_BOARD_NAME, "8C15"),
+ },
+ },
+ {}
+};
+#endif
+
#define DEVICE_MODE_TABLET 0x06
#define CPU_FAN 0
@@ -573,6 +651,7 @@ enum pwm_modes {
struct hp_wmi_hwmon_priv {
struct mutex lock; /* protects mode, pwm */
+ int fan_count;
u8 min_rpm;
u8 max_rpm;
u8 mode;
@@ -691,8 +770,9 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
goto out_free;
}
- if (obj->type != ACPI_TYPE_BUFFER) {
- pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret);
+ if (obj->type != ACPI_TYPE_BUFFER ||
+ obj->buffer.length < sizeof(*bios_return)) {
+ pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
ret = -EINVAL;
goto out_free;
}
@@ -711,7 +791,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
if (!outsize)
goto out_free;
- actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
+ actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return));
memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
memset(buffer + actual_outsize, 0, outsize - actual_outsize);
@@ -789,6 +869,200 @@ static int hp_wmi_read_int(int query)
return val;
}
+#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
+/* Protects hp_wmi_charge_behaviour */
+static DEFINE_MUTEX(hp_wmi_charge_lock);
+static enum power_supply_charge_behaviour hp_wmi_charge_behaviour =
+ POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+
+static int hp_wmi_charge_mode_to_behaviour(int mode,
+ enum power_supply_charge_behaviour *behaviour)
+{
+ switch (mode) {
+ case HPWMI_CHARGE_MODE_READ_AUTO:
+ case HPWMI_CHARGE_MODE_READ_CHARGING:
+ /*
+ * GBCO 0x01 reports active charging, rather than a separate
+ * charge policy. Treat it as automatic charging.
+ */
+ *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ return 0;
+ case HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE:
+ /*
+ * Firmware also returns 0x02 while running on battery alone.
+ * Treat it as force-discharge so a persisted AC policy is not
+ * lost across reboot.
+ */
+ *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ return 0;
+ case HPWMI_CHARGE_MODE_READ_INHIBIT:
+ case HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM:
+ case HPWMI_CHARGE_MODE_READ_INHIBIT_EXT:
+ *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hp_wmi_charge_mode_read(void)
+{
+ int val = 0, ret;
+
+ ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_READ,
+ &val, zero_if_sup(val), sizeof(val));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ return FIELD_GET(HPWMI_CHARGE_MODE_READ_MASK, val);
+}
+
+static int hp_wmi_charge_mode_write(u32 mode)
+{
+ int ret;
+
+ mode = FIELD_PREP(HPWMI_CHARGE_MODE_MASK, mode);
+ ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_WRITE,
+ &mode, sizeof(mode), 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int hp_wmi_charge_set_behaviour(enum power_supply_charge_behaviour behaviour)
+{
+ int ret;
+
+ guard(mutex)(&hp_wmi_charge_lock);
+
+ switch (behaviour) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_AUTO);
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_INHIBIT);
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_FORCE_DISCHARGE);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ hp_wmi_charge_behaviour = behaviour;
+
+ return 0;
+}
+
+static int hp_wmi_charge_get_property(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)
+ return -EINVAL;
+
+ guard(mutex)(&hp_wmi_charge_lock);
+ val->intval = hp_wmi_charge_behaviour;
+
+ return 0;
+}
+
+static int hp_wmi_charge_set_property(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)
+ return -EINVAL;
+
+ return hp_wmi_charge_set_behaviour(val->intval);
+}
+
+static int hp_wmi_charge_property_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
+}
+
+static const enum power_supply_property hp_wmi_charge_properties[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+static const struct power_supply_ext hp_wmi_charge_extension = {
+ .name = "hp-wmi-charge-control",
+ .properties = hp_wmi_charge_properties,
+ .num_properties = ARRAY_SIZE(hp_wmi_charge_properties),
+ .charge_behaviours = HPWMI_CHARGE_BEHAVIOURS,
+ .get_property = hp_wmi_charge_get_property,
+ .set_property = hp_wmi_charge_set_property,
+ .property_is_writeable = hp_wmi_charge_property_is_writeable,
+};
+
+static int hp_wmi_charge_add_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ return power_supply_register_extension(battery, &hp_wmi_charge_extension,
+ &hp_wmi_platform_dev->dev, NULL);
+}
+
+static int hp_wmi_charge_remove_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &hp_wmi_charge_extension);
+ return 0;
+}
+
+static struct acpi_battery_hook hp_wmi_charge_battery_hook = {
+ .name = "HP WMI charge control",
+ .add_battery = hp_wmi_charge_add_battery,
+ .remove_battery = hp_wmi_charge_remove_battery,
+};
+
+static int __init hp_wmi_charge_control_setup(struct device *dev)
+{
+ enum power_supply_charge_behaviour behaviour;
+ int ret;
+
+ if (!dmi_check_system(hp_wmi_charge_control_quirks))
+ return 0;
+
+ /*
+ * Firmware retains inhibit across reboot, so seed from GBCO rather
+ * than assuming auto.
+ */
+ ret = hp_wmi_charge_mode_read();
+ if (ret < 0)
+ return 0;
+
+ ret = hp_wmi_charge_mode_to_behaviour(ret, &behaviour);
+ if (ret)
+ return 0;
+
+ scoped_guard(mutex, &hp_wmi_charge_lock)
+ hp_wmi_charge_behaviour = behaviour;
+
+ return devm_battery_hook_register(dev, &hp_wmi_charge_battery_hook);
+}
+#else
+static int __init hp_wmi_charge_control_setup(struct device *dev)
+{
+ return 0;
+}
+#endif
+
static int hp_wmi_get_dock_state(void)
{
int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY);
@@ -2508,7 +2782,6 @@ static int thermal_profile_setup(struct platform_device *device)
return PTR_ERR(platform_profile_device);
pr_info("Registered as platform profile handler\n");
- platform_profile_support = true;
return 0;
}
@@ -2540,6 +2813,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
if (err < 0)
return err;
+ err = hp_wmi_charge_control_setup(&device->dev);
+ if (err)
+ return err;
+
thermal_profile_setup(device);
return 0;
@@ -2548,7 +2825,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
static void __exit hp_wmi_bios_remove(struct platform_device *device)
{
int i;
- struct hp_wmi_hwmon_priv *priv;
for (i = 0; i < rfkill2_count; i++) {
rfkill_unregister(rfkill2[i].rfkill);
@@ -2567,10 +2843,6 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
rfkill_unregister(wwan_rfkill);
rfkill_destroy(wwan_rfkill);
}
-
- priv = platform_get_drvdata(device);
- if (priv)
- cancel_delayed_work_sync(&priv->keep_alive_dwork);
}
static int hp_wmi_resume_handler(struct device *device)
@@ -2679,12 +2951,18 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
+ const struct hp_wmi_hwmon_priv *priv = data;
+
switch (type) {
case hwmon_pwm:
+ if (priv->fan_count <= 0)
+ return 0;
if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported())
return 0;
return 0644;
case hwmon_fan:
+ if (priv->fan_count <= 0)
+ return 0;
if (hp_wmi_fan_control_supported()) {
if (hp_wmi_get_active_fan_speed(channel) >= 0)
return 0444;
@@ -2853,6 +3131,7 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
/* Default behaviour on hwmon init is automatic mode */
priv->mode = PWM_MODE_AUTO;
+ priv->fan_count = hp_wmi_get_fan_count_userdefine_trigger();
/* Bypass devices without fan control support. */
if (!hp_wmi_fan_table_supported())
@@ -2918,6 +3197,17 @@ static int hp_wmi_hwmon_init(void)
ret = hp_wmi_setup_fan_settings(priv);
if (ret)
return ret;
+
+ /*
+ * Set up the work before registering hwmon so that, on teardown,
+ * it is cancelled only after the sysfs writers that schedule it
+ * are gone.
+ */
+ ret = devm_delayed_work_autocancel(dev, &priv->keep_alive_dwork,
+ hp_wmi_hwmon_keep_alive_handler);
+ if (ret)
+ return ret;
+
hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
&chip_info, NULL);
@@ -2926,8 +3216,11 @@ static int hp_wmi_hwmon_init(void)
return PTR_ERR(hwmon);
}
- INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
- platform_set_drvdata(hp_wmi_platform_dev, priv);
+ if (priv->fan_count <= 0) {
+ dev_info(dev, "No usable fans reported by WMI, disabling fan support\n");
+ return 0;
+ }
+
ret = hp_wmi_apply_fan_settings(priv);
if (ret)
dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret);
diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c
index 39b73dc473f1..40e1b9fb1b6d 100644
--- a/drivers/platform/x86/hp/hp_accel.c
+++ b/drivers/platform/x86/hp/hp_accel.c
@@ -122,7 +122,7 @@ static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret)
static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val)
{
struct acpi_device *dev = lis3->bus_priv;
- unsigned long long ret; /* Not used when writting */
+ unsigned long long ret; /* Not used when writing */
union acpi_object in_obj[2];
struct acpi_object_list args = { 2, in_obj };
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index d6aaf14d66a5..96ee9a97c9b8 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -79,6 +79,7 @@ static const struct key_entry huawei_wmi_keymap[] = {
{ KE_KEY, 0x285, { KEY_VOLUMEDOWN } },
{ KE_KEY, 0x286, { KEY_VOLUMEUP } },
{ KE_KEY, 0x287, { KEY_MICMUTE } },
+ { KE_KEY, 0x288, { KEY_CAMERA_ACCESS_TOGGLE } },
{ KE_KEY, 0x289, { KEY_WLAN } },
// Huawei |M| key
{ KE_KEY, 0x28a, { KEY_CONFIG } },
diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c
index 139956168cf9..82fda8d9178b 100644
--- a/drivers/platform/x86/ibm_rtl.c
+++ b/drivers/platform/x86/ibm_rtl.c
@@ -234,9 +234,9 @@ static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = {
};
static int __init ibm_rtl_init(void) {
- unsigned long ebda_addr, ebda_size;
+ unsigned long ebda_addr, ebda_size, offset;
unsigned int ebda_kb;
- int ret = -ENODEV, i;
+ int ret = -ENODEV;
if (force)
pr_warn("module loaded by force\n");
@@ -271,9 +271,9 @@ static int __init ibm_rtl_init(void) {
return -ENOMEM;
/* search for the _RTL_ signature at the start of the table */
- for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
+ for (offset = 0; offset + sizeof(*rtl_table) <= ebda_size; offset += 4) {
struct ibm_rtl_table __iomem * tmp;
- tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int));
+ tmp = (struct ibm_rtl_table __iomem *) (ebda_map + offset);
if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
phys_addr_t addr;
unsigned int plen;
diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig
index 2900407d6095..ea6dc7b50d90 100644
--- a/drivers/platform/x86/intel/Kconfig
+++ b/drivers/platform/x86/intel/Kconfig
@@ -209,11 +209,15 @@ config INTEL_SMARTCONNECT
config INTEL_TPMI_POWER_DOMAINS
tristate
+config INTEL_TPMI_COMMON
+ tristate
+
config INTEL_TPMI
tristate "Intel Topology Aware Register and PM Capsule Interface (TPMI)"
depends on INTEL_VSEC
depends on X86_64
select INTEL_TPMI_POWER_DOMAINS
+ select INTEL_TPMI_COMMON
help
The Intel Topology Aware Register and PM Capsule Interface (TPMI),
provides enumerable MMIO interface for power management features.
diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile
index 138b13756158..d2c63a136e51 100644
--- a/drivers/platform/x86/intel/Makefile
+++ b/drivers/platform/x86/intel/Makefile
@@ -40,6 +40,7 @@ intel-target-$(CONFIG_INTEL_PUNIT_IPC) += punit_ipc.o
# TPMI drivers
intel-target-$(CONFIG_INTEL_PLR_TPMI) += plr_tpmi.o
intel-target-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += tpmi_power_domains.o
+intel-target-$(CONFIG_INTEL_TPMI_COMMON) += tpmi_common.o
intel-target-$(CONFIG_INTEL_TPMI) += vsec_tpmi.o
# Intel Uncore drivers
diff --git a/drivers/platform/x86/intel/bxtwc_tmu.c b/drivers/platform/x86/intel/bxtwc_tmu.c
index b3666704d85b..73d7f71b2fd0 100644
--- a/drivers/platform/x86/intel/bxtwc_tmu.c
+++ b/drivers/platform/x86/intel/bxtwc_tmu.c
@@ -64,11 +64,8 @@ static int bxt_wcove_tmu_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(&pdev->dev, wctmu->irq,
NULL, bxt_wcove_tmu_irq_handler,
IRQF_ONESHOT, "bxt_wcove_tmu", wctmu);
- if (ret) {
- dev_err(&pdev->dev, "request irq failed: %d,virq: %d\n",
- ret, wctmu->irq);
+ if (ret)
return ret;
- }
/* Unmask TMU second level Wake & System alarm */
regmap_update_bits(wctmu->regmap, BXTWC_MTMUIRQ_REG,
diff --git a/drivers/platform/x86/intel/bytcrc_pwrsrc.c b/drivers/platform/x86/intel/bytcrc_pwrsrc.c
index 68ac040082df..1f39850b80fd 100644
--- a/drivers/platform/x86/intel/bytcrc_pwrsrc.c
+++ b/drivers/platform/x86/intel/bytcrc_pwrsrc.c
@@ -221,7 +221,7 @@ static int crc_pwrsrc_probe(struct platform_device *pdev)
crc_pwrsrc_irq_handler,
IRQF_ONESHOT, KBUILD_MODNAME, data);
if (ret)
- return dev_err_probe(dev, ret, "requesting IRQ\n");
+ return ret;
}
data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
diff --git a/drivers/platform/x86/intel/crystal_cove_charger.c b/drivers/platform/x86/intel/crystal_cove_charger.c
index e4299cfa2205..425c7eb32442 100644
--- a/drivers/platform/x86/intel/crystal_cove_charger.c
+++ b/drivers/platform/x86/intel/crystal_cove_charger.c
@@ -135,7 +135,7 @@ static int crystal_cove_charger_probe(struct platform_device *pdev)
crystal_cove_charger_irq,
IRQF_ONESHOT, KBUILD_MODNAME, charger);
if (ret)
- return dev_err_probe(&pdev->dev, ret, "requesting irq\n");
+ return ret;
return 0;
}
diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c
index acd42e639fa4..6024bf29c4df 100644
--- a/drivers/platform/x86/intel/hid.c
+++ b/drivers/platform/x86/intel/hid.c
@@ -165,6 +165,13 @@ static const struct dmi_system_id button_array_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 440 G1"),
},
},
+ {
+ .ident = "HP EliteBook x360 1030 G2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook x360 1030 G2"),
+ },
+ },
{ }
};
diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c
index 562e88025643..96b36cfcd9e4 100644
--- a/drivers/platform/x86/intel/int0002_vgpio.c
+++ b/drivers/platform/x86/intel/int0002_vgpio.c
@@ -208,10 +208,8 @@ static int int0002_probe(struct platform_device *pdev)
*/
ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002",
chip);
- if (ret) {
- dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret);
+ if (ret)
return ret;
- }
girq = &chip->irq;
gpio_irq_chip_set_chip(girq, &int0002_irqchip);
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 9f77c0716e59..71be313bdf0e 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1849,6 +1849,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev),
+ X86_MATCH_VFM(INTEL_PANTHERLAKE_R, &ptl_pmc_dev),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev),
X86_MATCH_VFM(INTEL_NOVALAKE, &nvl_s_pmc_dev),
X86_MATCH_VFM(INTEL_NOVALAKE_L, &nvl_h_pmc_dev),
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index b4c7399f8369..e55164e5807f 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -323,7 +323,7 @@ enum ppfear_regs {
#define NVL_LPM_LIVE_STATUS_OFFSET 0x1760
#define NVL_LPM_NUM_MAPS 15
#define NVL_PCDH_NUM_S0IX_BLOCKER 107
-#define NVL_PCDS_NUM_S0IX_BLOCKER 71
+#define NVL_PCDS_NUM_S0IX_BLOCKER 70
#define NVL_PCHS_NUM_S0IX_BLOCKER 54
#define NVL_PCDS_PMC_LTR_RESERVED 0x1bac
#define NVL_PCDH_BLK_REQ_OFFSET 53
diff --git a/drivers/platform/x86/intel/pmc/nvl.c b/drivers/platform/x86/intel/pmc/nvl.c
index 8dabf2511dd9..1ed02332dbfc 100644
--- a/drivers/platform/x86/intel/pmc/nvl.c
+++ b/drivers/platform/x86/intel/pmc/nvl.c
@@ -14,7 +14,7 @@
/* PMC SSRAM PMT Telemetry GUIDS */
#define PCDH_LPM_REQ_GUID 0x01093101
#define PCHS_LPM_REQ_GUID 0x01092101
-#define PCDS_LPM_REQ_GUID 0x01091102
+#define PCDS_LPM_REQ_GUID 0x01091103
/*
* Die Mapping to Product.
diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
index d0ab8e33c62a..2b11c4012f6a 100644
--- a/drivers/platform/x86/intel/pmt/class.c
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -12,6 +12,7 @@
#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/ioport.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mm.h>
@@ -100,7 +101,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
if (count > entry->size - off)
count = entry->size - off;
- count = pmt_telem_read_mmio(entry->ep->dev, entry->cb, entry->header.guid, buf,
+ count = pmt_telem_read_mmio(entry->dev, entry->cb, entry->header.guid, buf,
entry->base, off, count);
return count;
@@ -286,8 +287,6 @@ static int pmt_resolve_access_pci(struct intel_pmt_entry *entry,
return -EINVAL;
}
- entry->pcidev = pci_dev;
-
return 0;
}
@@ -365,6 +364,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
entry->guid = header->guid;
entry->size = header->size;
entry->cb = ivdev->priv_data;
+ entry->dev = ivdev->dev;
return 0;
}
@@ -374,7 +374,6 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
struct device *parent)
{
struct intel_vsec_device *ivdev = dev_to_ivdev(parent);
- struct resource res = {0};
struct device *dev;
int ret;
@@ -404,20 +403,32 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
if (!entry->size)
return 0;
- res.start = entry->base_addr;
- res.end = res.start + entry->size - 1;
- res.flags = IORESOURCE_MEM;
+ /*
+ * The read_telem callback is responsible for this mapping, and may have
+ * different requirements for use. If the callback is present do not
+ * create the map.
+ */
+ if (!(entry->cb && entry->cb->read_telem)) {
+ struct resource res = DEFINE_RES_MEM(entry->base_addr, entry->size);
- entry->base = devm_ioremap_resource(dev, &res);
- if (IS_ERR(entry->base)) {
- ret = PTR_ERR(entry->base);
- goto fail_ioremap;
+ entry->base = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(entry->base)) {
+ ret = PTR_ERR(entry->base);
+ goto fail_ioremap;
+ }
}
sysfs_bin_attr_init(&entry->pmt_bin_attr);
entry->pmt_bin_attr.attr.name = ns->name;
entry->pmt_bin_attr.attr.mode = 0440;
- entry->pmt_bin_attr.mmap = intel_pmt_mmap;
+
+ /*
+ * The read_telem callback controls access to the memory area. Block
+ * usage if the read_telem callback is present.
+ */
+ if (!(entry->cb && entry->cb->read_telem))
+ entry->pmt_bin_attr.mmap = intel_pmt_mmap;
+
entry->pmt_bin_attr.read = intel_pmt_read;
entry->pmt_bin_attr.size = entry->size;
diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h
index a0ece4fc3837..aeb04afb67cf 100644
--- a/drivers/platform/x86/intel/pmt/class.h
+++ b/drivers/platform/x86/intel/pmt/class.h
@@ -20,7 +20,6 @@
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
struct device;
-struct pci_dev;
extern struct class intel_pmt_class;
struct telem_endpoint {
@@ -42,14 +41,14 @@ struct intel_pmt_header {
struct intel_pmt_entry {
struct telem_endpoint *ep;
- struct pci_dev *pcidev;
+ struct device *dev;
struct intel_pmt_header header;
u32 disc_header[PMT_DISC_DWORDS];
struct bin_attribute pmt_bin_attr;
const struct attribute_group *attr_grp;
struct kobject *kobj;
void __iomem *disc_table;
- void __iomem *base;
+ void __iomem *base; /* unused if cb->read_telem is in use */
struct pmt_callbacks *cb;
unsigned long base_addr;
size_t size;
diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c
index f936daf99e4d..d7018b495217 100644
--- a/drivers/platform/x86/intel/pmt/crashlog.c
+++ b/drivers/platform/x86/intel/pmt/crashlog.c
@@ -11,10 +11,12 @@
#include <linux/auxiliary_bus.h>
#include <linux/cleanup.h>
#include <linux/intel_vsec.h>
+#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/overflow.h>
@@ -124,12 +126,36 @@ struct pmt_crashlog_priv {
* I/O
*/
+static int pmt_crashlog_read_reg(struct intel_pmt_entry *entry, u32 *reg, u32 offset)
+{
+ int ret;
+
+ *reg = 0;
+
+ if (entry->cb && entry->cb->read_reg) {
+ ret = entry->cb->read_reg(entry->dev, entry->header.guid, reg, offset);
+ if (ret) {
+ dev_err_ratelimited(entry->dev, "failed to read reg: %d\n", ret);
+ return ret;
+ }
+ } else {
+ *reg = readl(entry->disc_table + offset);
+ }
+
+ return 0;
+}
+
/* Read, modify, write the control register, setting or clearing @bit based on @set */
-static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
+static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
{
const struct crashlog_control *control = &crashlog->info->control;
struct intel_pmt_entry *entry = &crashlog->entry;
- u32 reg = readl(entry->disc_table + control->offset);
+ u32 reg;
+ int ret;
+
+ ret = pmt_crashlog_read_reg(entry, &reg, control->offset);
+ if (ret)
+ return ret;
reg &= ~control->trigger_mask;
@@ -138,28 +164,46 @@ static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
else
reg &= ~bit;
- writel(reg, entry->disc_table + control->offset);
+ if (entry->cb && entry->cb->write_reg) {
+ ret = entry->cb->write_reg(entry->dev, entry->header.guid, reg, control->offset);
+ if (ret) {
+ dev_err_ratelimited(entry->dev, "failed to write reg: %d\n", ret);
+ return ret;
+ }
+ } else {
+ writel(reg, entry->disc_table + control->offset);
+ }
+
+ return 0;
}
/* Read the status register and see if the specified @bit is set */
-static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
+static int pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit, bool *state)
{
const struct crashlog_status *status = &crashlog->info->status;
- u32 reg = readl(crashlog->entry.disc_table + status->offset);
+ struct intel_pmt_entry *entry = &crashlog->entry;
+ u32 reg;
+ int ret;
+
+ ret = pmt_crashlog_read_reg(entry, &reg, status->offset);
+ if (ret)
+ return ret;
+
+ *state = !!(reg & bit);
- return !!(reg & bit);
+ return 0;
}
-static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
+static int pmt_crashlog_complete(struct crashlog_entry *crashlog, bool *state)
{
/* return current value of the crashlog complete flag */
- return pmt_crashlog_rc(crashlog, crashlog->info->status.complete);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.complete, state);
}
-static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
+static int pmt_crashlog_disabled(struct crashlog_entry *crashlog, bool *state)
{
/* return current value of the crashlog disabled flag */
- return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled, state);
}
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version)
@@ -181,50 +225,50 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_typ
return false;
}
-static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
- bool disable)
+static int pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
+ bool disable)
{
- pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
+ return pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
}
-static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
+static int pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
{
- pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
+ return pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
}
-static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
+static int pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
{
- pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
+ return pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
}
-static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog)
+static int pmt_crashlog_cleared(struct crashlog_entry *crashlog, bool *state)
{
- return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared, state);
}
-static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog)
+static int pmt_crashlog_consumed(struct crashlog_entry *crashlog, bool *state)
{
- return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed, state);
}
-static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
+static int pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
{
- pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
+ return pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
}
-static bool pmt_crashlog_error(struct crashlog_entry *crashlog)
+static int pmt_crashlog_error(struct crashlog_entry *crashlog, bool *state)
{
- return pmt_crashlog_rc(crashlog, crashlog->info->status.error);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.error, state);
}
-static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog)
+static int pmt_crashlog_rearm(struct crashlog_entry *crashlog, bool *state)
{
- return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed, state);
}
-static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
+static int pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
{
- pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
+ return pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
}
/*
@@ -234,7 +278,12 @@ static ssize_t
clear_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
- bool cleared = pmt_crashlog_cleared(crashlog);
+ bool cleared;
+ int ret;
+
+ ret = pmt_crashlog_cleared(crashlog, &cleared);
+ if (ret)
+ return ret;
return sysfs_emit(buf, "%d\n", cleared);
}
@@ -245,13 +294,13 @@ clear_store(struct device *dev, struct device_attribute *attr,
{
struct crashlog_entry *crashlog;
bool clear;
- int result;
+ int ret;
crashlog = dev_get_drvdata(dev);
- result = kstrtobool(buf, &clear);
- if (result)
- return result;
+ ret = kstrtobool(buf, &clear);
+ if (ret)
+ return ret;
/* set bit only */
if (!clear)
@@ -259,9 +308,9 @@ clear_store(struct device *dev, struct device_attribute *attr,
guard(mutex)(&crashlog->control_mutex);
- pmt_crashlog_set_clear(crashlog);
+ ret = pmt_crashlog_set_clear(crashlog);
- return count;
+ return ret ?: count;
}
static DEVICE_ATTR_RW(clear);
@@ -269,7 +318,12 @@ static ssize_t
consumed_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
- bool consumed = pmt_crashlog_consumed(crashlog);
+ bool consumed;
+ int ret;
+
+ ret = pmt_crashlog_consumed(crashlog, &consumed);
+ if (ret)
+ return ret;
return sysfs_emit(buf, "%d\n", consumed);
}
@@ -279,14 +333,16 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu
size_t count)
{
struct crashlog_entry *crashlog;
+ bool complete;
bool consumed;
- int result;
+ bool disabled;
+ int ret;
crashlog = dev_get_drvdata(dev);
- result = kstrtobool(buf, &consumed);
- if (result)
- return result;
+ ret = kstrtobool(buf, &consumed);
+ if (ret)
+ return ret;
/* set bit only */
if (!consumed)
@@ -294,15 +350,21 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu
guard(mutex)(&crashlog->control_mutex);
- if (pmt_crashlog_disabled(crashlog))
+ ret = pmt_crashlog_disabled(crashlog, &disabled);
+ if (ret)
+ return ret;
+ if (disabled)
return -EBUSY;
- if (!pmt_crashlog_complete(crashlog))
+ ret = pmt_crashlog_complete(crashlog, &complete);
+ if (ret)
+ return ret;
+ if (!complete)
return -EEXIST;
- pmt_crashlog_set_consumed(crashlog);
+ ret = pmt_crashlog_set_consumed(crashlog);
- return count;
+ return ret ?: count;
}
static DEVICE_ATTR_RW(consumed);
@@ -310,9 +372,14 @@ static ssize_t
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
- bool enabled = !pmt_crashlog_disabled(crashlog);
+ bool disabled;
+ int ret;
+
+ ret = pmt_crashlog_disabled(crashlog, &disabled);
+ if (ret)
+ return ret;
- return sprintf(buf, "%d\n", enabled);
+ return sysfs_emit(buf, "%d\n", !disabled);
}
static ssize_t
@@ -321,19 +388,19 @@ enable_store(struct device *dev, struct device_attribute *attr,
{
struct crashlog_entry *crashlog;
bool enabled;
- int result;
+ int ret;
crashlog = dev_get_drvdata(dev);
- result = kstrtobool(buf, &enabled);
- if (result)
- return result;
+ ret = kstrtobool(buf, &enabled);
+ if (ret)
+ return ret;
guard(mutex)(&crashlog->control_mutex);
- pmt_crashlog_set_disable(crashlog, !enabled);
+ ret = pmt_crashlog_set_disable(crashlog, !enabled);
- return count;
+ return ret ?: count;
}
static DEVICE_ATTR_RW(enable);
@@ -341,7 +408,12 @@ static ssize_t
error_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
- bool error = pmt_crashlog_error(crashlog);
+ bool error;
+ int ret;
+
+ ret = pmt_crashlog_error(crashlog, &error);
+ if (ret)
+ return ret;
return sysfs_emit(buf, "%d\n", error);
}
@@ -351,7 +423,12 @@ static ssize_t
rearm_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
- int rearmed = pmt_crashlog_rearm(crashlog);
+ bool rearmed;
+ int ret;
+
+ ret = pmt_crashlog_rearm(crashlog, &rearmed);
+ if (ret)
+ return ret;
return sysfs_emit(buf, "%d\n", rearmed);
}
@@ -362,13 +439,13 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
{
struct crashlog_entry *crashlog;
bool rearm;
- int result;
+ int ret;
crashlog = dev_get_drvdata(dev);
- result = kstrtobool(buf, &rearm);
- if (result)
- return result;
+ ret = kstrtobool(buf, &rearm);
+ if (ret)
+ return ret;
/* set only */
if (!rearm)
@@ -376,9 +453,9 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
guard(mutex)(&crashlog->control_mutex);
- pmt_crashlog_set_rearm(crashlog);
+ ret = pmt_crashlog_set_rearm(crashlog);
- return count;
+ return ret ?: count;
}
static DEVICE_ATTR_RW(rearm);
@@ -387,11 +464,15 @@ trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct crashlog_entry *crashlog;
bool trigger;
+ int ret;
crashlog = dev_get_drvdata(dev);
- trigger = pmt_crashlog_complete(crashlog);
- return sprintf(buf, "%d\n", trigger);
+ ret = pmt_crashlog_complete(crashlog, &trigger);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", trigger);
}
static ssize_t
@@ -399,33 +480,41 @@ trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct crashlog_entry *crashlog;
+ bool complete;
+ bool disabled;
bool trigger;
- int result;
+ int ret;
crashlog = dev_get_drvdata(dev);
- result = kstrtobool(buf, &trigger);
- if (result)
- return result;
+ ret = kstrtobool(buf, &trigger);
+ if (ret)
+ return ret;
guard(mutex)(&crashlog->control_mutex);
/* if device is currently disabled, return busy */
- if (pmt_crashlog_disabled(crashlog))
+ ret = pmt_crashlog_disabled(crashlog, &disabled);
+ if (ret)
+ return ret;
+ if (disabled)
return -EBUSY;
if (!trigger) {
- pmt_crashlog_set_clear(crashlog);
- return count;
+ ret = pmt_crashlog_set_clear(crashlog);
+ return ret ?: count;
}
/* we cannot trigger a new crash if one is still pending */
- if (pmt_crashlog_complete(crashlog))
+ ret = pmt_crashlog_complete(crashlog, &complete);
+ if (ret)
+ return ret;
+ if (complete)
return -EEXIST;
- pmt_crashlog_set_execute(crashlog);
+ ret = pmt_crashlog_set_execute(crashlog);
- return count;
+ return ret ?: count;
}
static DEVICE_ATTR_RW(trigger);
diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c
index c482368bfaae..f4203d240f54 100644
--- a/drivers/platform/x86/intel/pmt/discovery.c
+++ b/drivers/platform/x86/intel/pmt/discovery.c
@@ -609,7 +609,7 @@ void intel_pmt_get_features(struct intel_pmt_entry *entry)
mutex_lock(&feature_list_lock);
list_for_each_entry(feature, &pmt_feature_list, list) {
- if (feature->priv->parent != entry->ep->dev)
+ if (feature->priv->parent != entry->dev)
continue;
pmt_get_features(entry, feature);
diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c
index 953f35b6daec..5423a4dfce08 100644
--- a/drivers/platform/x86/intel/pmt/telemetry.c
+++ b/drivers/platform/x86/intel/pmt/telemetry.c
@@ -329,6 +329,9 @@ int pmt_telem_read32(struct telem_endpoint *ep, u32 id, u32 *data, u32 count)
if (!ep->present)
return -ENODEV;
+ if (!ep->base)
+ return -EIO;
+
offset = SAMPLE_ID_OFFSET32(id);
size = ep->header.size;
diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c
index 6d770b950dfb..a3d2b0b64135 100644
--- a/drivers/platform/x86/intel/punit_ipc.c
+++ b/drivers/platform/x86/intel/punit_ipc.c
@@ -250,10 +250,8 @@ static int intel_punit_ipc_probe(struct platform_device *pdev)
ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
IRQF_NO_SUSPEND, "intel_punit_ipc",
punit_ipcdev);
- if (ret) {
- dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
+ if (ret)
return ret;
- }
punit_ipcdev->irq = irq;
}
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
index 1c48bf6d5457..4c04f0bc9b47 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
@@ -7,6 +7,7 @@
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
*/
+#include <linux/acpi.h>
#include <linux/cpufeature.h>
#include <linux/cpuhotplug.h>
#include <linux/fs.h>
@@ -774,6 +775,54 @@ void isst_if_cdev_unregister(int device_type)
}
EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
+#ifdef CONFIG_ACPI
+
+static bool isst_acpi_pm_profile_server(void)
+{
+ if (acpi_gbl_FADT.preferred_profile == PM_ENTERPRISE_SERVER ||
+ acpi_gbl_FADT.preferred_profile == PM_PERFORMANCE_SERVER)
+ return true;
+
+ return false;
+}
+
+#else
+
+static bool isst_acpi_pm_profile_server(void)
+{
+ return false;
+}
+
+#endif
+
+static const struct x86_cpu_id isst_allowed_families[] = {
+ X86_MATCH_VENDOR_FAM(INTEL, 19, NULL),
+ {}
+};
+
+static bool isst_features_allowed(void)
+{
+ u64 data;
+
+ /* For hypervisors, explicit model addition is required */
+ if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR))
+ return false;
+
+ /* SST is a server only feature */
+ if (!isst_acpi_pm_profile_server())
+ return false;
+
+ /*
+ * Family 19 and the checks above guarantee the SST enumeration
+ * interfaces are present
+ */
+ if (!x86_match_cpu(isst_allowed_families))
+ return false;
+
+ /* MSR_PM_LOGICAL_ID is required to get PM logical CPU ID */
+ return !rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
+}
+
#define SST_HPM_SUPPORTED 0x01
#define SST_MBOX_SUPPORTED 0x02
@@ -798,8 +847,13 @@ static int __init isst_if_common_init(void)
const struct x86_cpu_id *id;
id = x86_match_cpu(isst_cpu_ids);
- if (!id)
+ if (!id) {
+ if (isst_features_allowed()) {
+ isst_hpm_support = true;
+ goto misc_reg;
+ }
return -ENODEV;
+ }
if (id->driver_data == SST_HPM_SUPPORTED) {
isst_hpm_support = true;
@@ -812,6 +866,7 @@ static int __init isst_if_common_init(void)
return -ENODEV;
}
+misc_reg:
return isst_misc_reg();
}
module_init(isst_if_common_init)
diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c
index f312864b8d07..62299524a07b 100644
--- a/drivers/platform/x86/intel/telemetry/core.c
+++ b/drivers/platform/x86/intel/telemetry/core.c
@@ -24,6 +24,10 @@ static struct telemetry_core_config telm_core_conf;
static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
+ if (!verbosity)
+ return -EINVAL;
+
+ *verbosity = 0;
return 0;
}
diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c
new file mode 100644
index 000000000000..b6b811016dfd
--- /dev/null
+++ b/drivers/platform/x86/intel/tpmi_common.c
@@ -0,0 +1,822 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Enumeration independent part of the Intel TPMI driver
+ *
+ * Copyright (c) 2026, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * The TPMI (Topology Aware Register and PM Capsule Interface) provides a
+ * flexible, extendable and PCIe enumerable MMIO interface for PM features.
+ *
+ * For example Intel RAPL (Running Average Power Limit) provides a MMIO
+ * interface using TPMI. This has advantage over traditional MSR
+ * (Model Specific Register) interface, where a thread needs to be scheduled
+ * on the target CPU to read or write. Also the RAPL features vary between
+ * CPU models, and hence lot of model specific code. Here TPMI provides an
+ * architectural interface by providing hierarchical tables and fields,
+ * which will not need any model specific implementation.
+ *
+ * The TPMI specification defines a PFS (PM Feature Structure) table.
+ * This table is present in the TPMI MMIO region. Each TPMI PM feature
+ * has one entry in the PFS with a unique TPMI ID and its access details.
+ *
+ * The names of the devices created for the PM features start with the
+ * "intel_vsec.tpmi-" prefix which is followed by a specific name of the
+ * given PM feature (for example, "intel_vsec.tpmi-rapl.0").
+ *
+ * The device nodes are create by using interface "intel_vsec_add_aux()"
+ * provided by the Intel VSEC driver.
+ */
+
+#include <linux/align.h>
+#include <linux/array_size.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/intel_tpmi.h>
+#include <linux/intel_vsec.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/pci.h>
+#include <linux/security.h>
+#include <linux/seq_file.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/string_helpers.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+
+#include "tpmi_common.h"
+
+/**
+ * struct intel_tpmi_pfs_entry - TPMI PM Feature Structure (PFS) entry
+ * @tpmi_id: TPMI feature identifier (what the feature is and its data format).
+ * @num_entries: Number of feature interface instances present in the PFS.
+ * This represents the maximum number of Power domains in the SoC.
+ * @entry_size: Interface instance entry size in 32-bit words.
+ * @cap_offset: Offset from the PM_Features base address to the base of the PM VSEC
+ * register bank in KB.
+ * @attribute: Feature attribute: 0=BIOS. 1=OS. 2-3=Reserved.
+ * @reserved: Bits for use in the future.
+ *
+ * Represents one TPMI feature entry data in the PFS retrieved as is
+ * from the hardware.
+ */
+struct intel_tpmi_pfs_entry {
+ u64 tpmi_id:8;
+ u64 num_entries:8;
+ u64 entry_size:16;
+ u64 cap_offset:16;
+ u64 attribute:2;
+ u64 reserved:14;
+} __packed;
+
+/**
+ * struct intel_tpmi_pm_feature - TPMI PM Feature information for a TPMI ID
+ * @pfs_header: PFS header retireved from the hardware.
+ * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially
+ * this offset = "Address" from VSEC header + PFS Capability
+ * offset for this feature entry.
+ *
+ * Represents TPMI instance information for one TPMI ID.
+ */
+struct intel_tpmi_pm_feature {
+ struct intel_tpmi_pfs_entry pfs_header;
+ u64 vsec_offset;
+};
+
+/**
+ * struct tpmi_info_header - CPU package ID to PCI device mapping information
+ * @fn: PCI function number
+ * @dev: PCI device number
+ * @bus: PCI bus number
+ * @pkg: CPU Package id
+ * @segment: PCI segment id
+ * @partition: Package Partition id
+ * @cdie_mask: Bitmap of compute dies in the current partition
+ * @reserved: Reserved for future use
+ * @lock: When set to 1 the register is locked and becomes read-only
+ * until next reset. Not for use by the OS driver.
+ *
+ * The structure to read hardware provided mapping information.
+ */
+struct tpmi_info_header {
+ u64 fn:3;
+ u64 dev:5;
+ u64 bus:8;
+ u64 pkg:8;
+ u64 segment:8;
+ u64 partition:2;
+ u64 cdie_mask:16;
+ u64 reserved:13;
+ u64 lock:1;
+} __packed;
+
+/**
+ * struct tpmi_feature_state - Structure to read hardware state of a feature
+ * @enabled: Enable state of a feature, 1: enabled, 0: disabled
+ * @reserved_1: Reserved for future use
+ * @write_blocked: Writes are blocked means all write operations are ignored
+ * @read_blocked: Reads are blocked means will read 0xFFs
+ * @pcs_select: Interface used by out of band software, not used in OS
+ * @reserved_2: Reserved for future use
+ * @id: TPMI ID of the feature
+ * @reserved_3: Reserved for future use
+ * @locked: When set to 1, OS can't change this register.
+ *
+ * The structure is used to read hardware state of a TPMI feature. This
+ * information is used for debug and restricting operations for this feature.
+ */
+struct tpmi_feature_state {
+ u32 enabled:1;
+ u32 reserved_1:3;
+ u32 write_blocked:1;
+ u32 read_blocked:1;
+ u32 pcs_select:1;
+ u32 reserved_2:1;
+ u32 id:8;
+ u32 reserved_3:15;
+ u32 locked:1;
+} __packed;
+
+/*
+ * Return the size of a single feature interface instance in bytes. The
+ * hardware reports it in u32 units. The value comes from trusted hardware,
+ * but is still worth verifying on pre silicon platforms, so return 0 when
+ * it is out of range.
+ */
+static u32 tpmi_get_single_entry_size(const struct intel_tpmi_pm_feature *pfs)
+{
+ if (pfs->pfs_header.entry_size > SZ_1K)
+ return 0;
+
+ return pfs->pfs_header.entry_size * sizeof(u32);
+}
+
+/* Used during auxbus device creation */
+static DEFINE_IDA(intel_vsec_tpmi_ida);
+
+static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list);
+
+int tpmi_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&tpmi_notify_list, nb);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI");
+
+int tpmi_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&tpmi_notify_list, nb);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI");
+
+struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
+{
+ struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
+
+ return vsec_dev->priv_data;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_platform_data, "INTEL_TPMI");
+
+int tpmi_get_resource_count(struct auxiliary_device *auxdev)
+{
+ struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
+
+ if (vsec_dev)
+ return vsec_dev->num_resources;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_count, "INTEL_TPMI");
+
+struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index)
+{
+ struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
+
+ if (vsec_dev && index < vsec_dev->num_resources)
+ return &vsec_dev->resource[index];
+
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, "INTEL_TPMI");
+
+/* TPMI Control Interface */
+
+#define TPMI_CONTROL_STATUS_OFFSET 0x00
+#define TPMI_COMMAND_OFFSET 0x08
+#define TMPI_CONTROL_DATA_VAL_OFFSET 0x0c
+
+/*
+ * Spec is calling for max 1 seconds to get ownership at the worst
+ * case. Read at 10 ms timeouts and repeat up to 1 second.
+ */
+#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC)
+#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC)
+
+#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC)
+#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC
+
+/* TPMI Control status register defines */
+
+#define TPMI_CONTROL_STATUS_RB BIT_ULL(0)
+
+#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4)
+#define TPMI_OWNER_NONE 0
+#define TPMI_OWNER_IN_BAND 1
+
+#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6)
+#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8)
+#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16)
+
+#define TPMI_CMD_PKT_LEN 2
+#define TPMI_CMD_STATUS_SUCCESS 0x40
+
+/* TPMI command data registers */
+#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0)
+#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40)
+
+/* Command to send via control interface */
+#define TPMI_CONTROL_GET_STATE_CMD 0x10
+
+#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40)
+
+#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16)
+
+/* Mutex to complete get feature status without interruption */
+static DEFINE_MUTEX(tpmi_dev_lock);
+
+static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner)
+{
+ u64 control;
+
+ return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
+ control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control),
+ TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US);
+}
+
+static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id,
+ struct tpmi_feature_state *feature_state)
+{
+ u64 control, data;
+ int ret;
+
+ if (!tpmi_info->tpmi_control_mem)
+ return -EFAULT;
+
+ guard(mutex)(&tpmi_dev_lock);
+
+ /* Wait for owner bit set to 0 (none) */
+ ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE);
+ if (ret)
+ return ret;
+
+ /*
+ * Set command id to 0x10 for TPMI_GET_STATE, with the feature id 32
+ * bits into the DATA offset, +8 for the feature_id field.
+ */
+ data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD) |
+ FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id);
+
+ /* Write at command offset for qword access */
+ writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
+
+ /* Wait for owner bit set to in-band */
+ ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND);
+ if (ret)
+ return ret;
+
+ /* Set Run Busy and packet length of 2 dwords */
+ control = TPMI_CONTROL_STATUS_RB |
+ FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN);
+
+ /* Write at status offset for qword access */
+ writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
+
+ /* Wait for Run Busy clear */
+ ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
+ control, !(control & TPMI_CONTROL_STATUS_RB),
+ TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US);
+ if (ret)
+ goto done_proc;
+
+ control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control);
+ if (control != TPMI_CMD_STATUS_SUCCESS) {
+ ret = -EBUSY;
+ goto done_proc;
+ }
+
+ /* Response is ready */
+ memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET,
+ sizeof(*feature_state));
+
+ ret = 0;
+
+done_proc:
+ /* Set CPL "completion" bit */
+ writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
+
+ return ret;
+}
+
+int tpmi_get_feature_status(struct auxiliary_device *auxdev,
+ int feature_id, bool *read_blocked, bool *write_blocked)
+{
+ struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent);
+ struct tpmi_feature_state feature_state;
+ int ret;
+
+ ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state);
+ if (ret)
+ return ret;
+
+ *read_blocked = feature_state.read_blocked;
+ *write_blocked = feature_state.write_blocked;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI");
+
+struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev)
+{
+ struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent);
+
+ return tpmi_info->dbgfs_dir;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_debugfs_dir, "INTEL_TPMI");
+
+static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused)
+{
+ struct intel_tpmi_info *tpmi_info = s->private;
+ int locked, disabled, read_blocked, write_blocked;
+ struct tpmi_feature_state feature_state;
+ struct intel_tpmi_pm_feature *pfs;
+ int ret;
+
+ seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start);
+ seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n");
+ for (int i = 0; i < tpmi_info->feature_count; i++) {
+ pfs = &tpmi_info->tpmi_features[i];
+ ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
+ if (ret) {
+ locked = 'U';
+ disabled = 'U';
+ read_blocked = 'U';
+ write_blocked = 'U';
+ } else {
+ disabled = feature_state.enabled ? 'N' : 'Y';
+ locked = feature_state.locked ? 'Y' : 'N';
+ read_blocked = feature_state.read_blocked ? 'Y' : 'N';
+ write_blocked = feature_state.write_blocked ? 'Y' : 'N';
+ }
+ seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%016llx\t%c\t%c\t\t%c\t\t%c\n",
+ pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries,
+ pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset,
+ pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled,
+ read_blocked, write_blocked);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg);
+
+#define MEM_DUMP_COLUMN_COUNT 8
+
+static int tpmi_mem_dump_show(struct seq_file *s, void *unused)
+{
+ size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32);
+ struct intel_tpmi_pm_feature *pfs = s->private;
+ void __iomem *mem;
+ u32 size;
+ u64 off;
+
+ size = tpmi_get_single_entry_size(pfs);
+ if (!size)
+ return -EIO;
+
+ u8 *buffer __free(kfree) = kmalloc(size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ off = pfs->vsec_offset;
+
+ guard(mutex)(&tpmi_dev_lock);
+
+ for (int count = 0; count < pfs->pfs_header.num_entries; count++) {
+ seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off);
+
+ mem = ioremap(off, size);
+ if (!mem)
+ return -ENOMEM;
+
+ memcpy_fromio(buffer, mem, size);
+
+ seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size,
+ false);
+
+ iounmap(mem);
+
+ off += size;
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump);
+
+static ssize_t mem_write_write(struct file *file, const char __user *userbuf, size_t len,
+ loff_t *ppos)
+{
+ struct seq_file *m = file->private_data;
+ struct intel_tpmi_pm_feature *pfs = m->private;
+ u32 addr, value, punit, size;
+ u32 num_elems;
+ void __iomem *mem;
+ int ret;
+
+ size = tpmi_get_single_entry_size(pfs);
+ if (!size)
+ return -EIO;
+
+ u32 *array __free(kfree) = NULL;
+ ret = parse_int_array_user(userbuf, len, (int **)&array);
+ if (ret < 0)
+ return ret;
+
+ num_elems = *array;
+ if (num_elems != 3)
+ return -EINVAL;
+
+ punit = array[1];
+ addr = array[2];
+ value = array[3];
+
+ if (!IS_ALIGNED(addr, sizeof(u32)))
+ return -EINVAL;
+
+ if (punit >= pfs->pfs_header.num_entries)
+ return -EINVAL;
+
+ if (addr >= size)
+ return -EINVAL;
+
+ guard(mutex)(&tpmi_dev_lock);
+
+ mem = ioremap(pfs->vsec_offset + punit * size, size);
+ if (!mem)
+ return -ENOMEM;
+
+ writel(value, mem + addr);
+
+ iounmap(mem);
+
+ return len;
+}
+
+static int mem_write_show(struct seq_file *s, void *unused)
+{
+ return 0;
+}
+DEFINE_SHOW_STORE_ATTRIBUTE(mem_write);
+
+static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info)
+{
+ char name[64];
+
+ scnprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent));
+ tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL);
+
+ debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops);
+
+ for (int i = 0; i < tpmi_info->feature_count; i++) {
+ struct intel_tpmi_pm_feature *pfs;
+ struct dentry *dir;
+
+ pfs = &tpmi_info->tpmi_features[i];
+ scnprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id);
+ dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir);
+
+ debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops);
+ debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_fops);
+ }
+}
+
+static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info,
+ struct intel_tpmi_pm_feature *pfs)
+{
+ void __iomem *mem;
+ u32 size;
+
+ size = tpmi_get_single_entry_size(pfs);
+ if (!size)
+ return;
+
+ mem = devm_ioremap(tpmi_info->tpmi_dev, pfs->vsec_offset, size);
+ if (!mem)
+ return;
+
+ /* mem is pointing to TPMI CONTROL base */
+ tpmi_info->tpmi_control_mem = mem;
+}
+
+/*
+ * The TPMI IDs are sparse, so the unused entries are left NULL and are
+ * rejected by the caller like any other unsupported feature.
+ */
+static const char * const intel_tpmi_names[] = {
+ [TPMI_ID_RAPL] = "tpmi-rapl",
+ [TPMI_ID_PEM] = "tpmi-pem",
+ [TPMI_ID_UNCORE] = "tpmi-uncore",
+ [TPMI_ID_SST] = "tpmi-sst",
+ [TPMI_ID_PLR] = "tpmi-plr",
+};
+
+static const char *intel_tpmi_name(enum intel_tpmi_id id)
+{
+ if (id >= ARRAY_SIZE(intel_tpmi_names))
+ return NULL;
+
+ return intel_tpmi_names[id];
+}
+
+static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
+ struct intel_tpmi_pm_feature *pfs,
+ u64 pfs_start)
+{
+ struct intel_vsec_device *feature_vsec_dev;
+ struct tpmi_feature_state feature_state;
+ struct resource *res;
+ const char *name;
+ int ret;
+
+ ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
+ if (ret)
+ return ret;
+
+ /*
+ * If not enabled, continue to look at other features in the PFS, so return -EOPNOTSUPP.
+ * This will not cause failure of loading of this driver.
+ */
+ if (!feature_state.enabled)
+ return -EOPNOTSUPP;
+
+ name = intel_tpmi_name(pfs->pfs_header.tpmi_id);
+ if (!name)
+ return -EOPNOTSUPP;
+
+ feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries);
+ if (!feature_vsec_dev)
+ return -ENOMEM;
+
+ feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
+ res = feature_vsec_dev->resource;
+
+ for (int i = 0; i < pfs->pfs_header.num_entries; i++) {
+ u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32);
+
+ res[i] = DEFINE_RES_MEM(pfs->vsec_offset + entry_size_bytes * i, entry_size_bytes);
+ }
+
+ feature_vsec_dev->dev = tpmi_info->parent;
+ feature_vsec_dev->priv_data = &tpmi_info->plat_info;
+ feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info);
+ feature_vsec_dev->ida = &intel_vsec_tpmi_ida;
+
+ /*
+ * intel_vsec_add_aux() is resource managed, no explicit
+ * delete is required on error or on module unload.
+ * feature_vsec_dev and res memory are also freed as part of
+ * device deletion.
+ *
+ * "name" must outlive the auxiliary device, as the auxiliary bus
+ * stores the pointer rather than a copy of the string.
+ */
+ return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name);
+}
+
+/*
+ * Create a device for every feature in the PFS that has one.
+ *
+ * A feature that is not supported is reported as -EOPNOTSUPP and skipped.
+ * Any other error fails the driver load. intel_vsec_add_aux() is resource
+ * managed, so the devices created before such a failure need no clean up
+ * here.
+ */
+static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info)
+{
+ int ret;
+
+ for (int i = 0; i < tpmi_info->feature_count; i++) {
+ ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i],
+ tpmi_info->pfs_start);
+ if (ret == -EOPNOTSUPP)
+ continue;
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#define TPMI_INFO_BUS_INFO_OFFSET 0x08
+#define TPMI_INFO_MAJOR_VERSION 0x00
+#define TPMI_INFO_MINOR_VERSION 0x02
+
+static int tpmi_process_info(struct intel_tpmi_info *tpmi_info,
+ struct intel_tpmi_pm_feature *pfs)
+{
+ struct tpmi_info_header header;
+ void __iomem *info_mem;
+ u64 feature_header;
+ int ret = 0;
+
+ info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32));
+ if (!info_mem)
+ return -ENOMEM;
+
+ feature_header = readq(info_mem);
+ if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) {
+ ret = -ENODEV;
+ goto error_info_header;
+ }
+
+ memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header));
+
+ tpmi_info->plat_info.package_id = header.pkg;
+ tpmi_info->plat_info.bus_number = header.bus;
+ tpmi_info->plat_info.device_number = header.dev;
+ tpmi_info->plat_info.function_number = header.fn;
+
+ if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) {
+ tpmi_info->plat_info.cdie_mask = header.cdie_mask;
+ tpmi_info->plat_info.partition = header.partition;
+ tpmi_info->plat_info.segment = header.segment;
+ }
+
+error_info_header:
+ iounmap(info_mem);
+
+ return ret;
+}
+
+static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size)
+{
+ void __iomem *pfs_mem;
+
+ pfs_mem = ioremap(start, size);
+ if (!pfs_mem)
+ return -ENOMEM;
+
+ memcpy_fromio(&pfs->pfs_header, pfs_mem, sizeof(pfs->pfs_header));
+
+ iounmap(pfs_mem);
+
+ return 0;
+}
+
+#define TPMI_CAP_OFFSET_UNIT 1024
+
+/**
+ * intel_tpmi_init() - Bring up a TPMI instance
+ * @tpmi_info: Caller allocated and zeroed state of the TPMI instance
+ * @tpmi_dev: Device representing the TPMI instance. Owns the devm
+ * allocations and parents the TPMI feature devices
+ * @parent: Device the TPMI instance was enumerated from
+ * @resource: Array of @feature_count MMIO resources, one per PFS entry
+ * @feature_count: Number of entries in @resource
+ *
+ * Walk the PFS of the TPMI instance described by @resource, register the
+ * debugfs interface and create a device for each supported PM feature.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev,
+ struct device *parent, struct resource *resource, int feature_count)
+{
+ struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL;
+ u64 pfs_start = 0;
+ int ret;
+
+ tpmi_info->tpmi_dev = tpmi_dev;
+ tpmi_info->parent = parent;
+ tpmi_info->feature_count = feature_count;
+ tpmi_info->resource = resource;
+
+ /*
+ * Seed the bus number from the enumerating device. It is only a
+ * fallback, tpmi_process_info() replaces it with the value the
+ * TPMI_INFO feature reports.
+ */
+ if (pci_dev)
+ tpmi_info->plat_info.bus_number = pci_dev->bus->number;
+
+ tpmi_info->tpmi_features = devm_kcalloc(tpmi_info->tpmi_dev, tpmi_info->feature_count,
+ sizeof(*tpmi_info->tpmi_features),
+ GFP_KERNEL);
+ if (!tpmi_info->tpmi_features)
+ return -ENOMEM;
+
+ for (int i = 0; i < tpmi_info->feature_count; i++) {
+ struct intel_tpmi_pm_feature *pfs;
+ struct resource *res;
+ u64 res_start;
+ int size, ret;
+
+ pfs = &tpmi_info->tpmi_features[i];
+
+ res = &tpmi_info->resource[i];
+ if (!res)
+ continue;
+
+ res_start = res->start;
+ size = resource_size(res);
+ if (size < 0)
+ continue;
+
+ ret = tpmi_fetch_pfs_header(pfs, res_start, size);
+ if (ret)
+ continue;
+
+ if (!pfs_start)
+ pfs_start = res_start;
+
+ pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset * TPMI_CAP_OFFSET_UNIT;
+
+ /*
+ * Process TPMI_INFO to get PCI device to CPU package ID.
+ * Device nodes for TPMI features are not created in this
+ * for loop. So, the mapping information will be available
+ * when actual device nodes created outside this
+ * loop via tpmi_create_devices().
+ */
+ if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) {
+ ret = tpmi_process_info(tpmi_info, pfs);
+ if (ret)
+ return ret;
+
+ /*
+ * The mapping is looked up by pci_dev, so it is only
+ * meaningful for PCI enumerated TPMI instances.
+ */
+ if (pci_dev) {
+ ret = intel_vsec_set_mapping(&tpmi_info->plat_info,
+ &pci_dev->dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)
+ tpmi_set_control_base(tpmi_info, pfs);
+ }
+
+ tpmi_info->pfs_start = pfs_start;
+
+ dev_set_drvdata(tpmi_info->tpmi_dev, tpmi_info);
+
+ /*
+ * Allow debugfs when security policy allows. Everything this debugfs
+ * interface provides, can also be done via /dev/mem access. If
+ * /dev/mem interface is locked, don't allow debugfs to present any
+ * information. Also check for CAP_SYS_RAWIO as /dev/mem interface.
+ */
+ if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO))
+ tpmi_dbgfs_register(tpmi_info);
+
+ ret = tpmi_create_devices(tpmi_info);
+ if (ret) {
+ debugfs_remove_recursive(tpmi_info->dbgfs_dir);
+ return ret;
+ }
+
+ blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, tpmi_info->tpmi_dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(intel_tpmi_init, "INTEL_TPMI");
+
+/**
+ * intel_tpmi_deinit() - Tear a TPMI instance down
+ * @tpmi_info: State of the TPMI instance, as passed to intel_tpmi_init()
+ *
+ * The TPMI feature devices and the devm allocations are released by the
+ * device core when the TPMI device goes away.
+ */
+void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info)
+{
+ blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, tpmi_info->tpmi_dev);
+
+ debugfs_remove_recursive(tpmi_info->dbgfs_dir);
+}
+EXPORT_SYMBOL_NS_GPL(intel_tpmi_deinit, "INTEL_TPMI");
+
+MODULE_IMPORT_NS("INTEL_VSEC");
+MODULE_DESCRIPTION("Intel TPMI core support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/tpmi_common.h b/drivers/platform/x86/intel/tpmi_common.h
new file mode 100644
index 000000000000..dd4bbe517e1c
--- /dev/null
+++ b/drivers/platform/x86/intel/tpmi_common.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel TPMI core interface for TPMI enumeration methods
+ *
+ * Copyright (c) 2026, Intel Corporation.
+ * All Rights Reserved.
+ */
+
+#ifndef _TPMI_COMMON_H_
+#define _TPMI_COMMON_H_
+
+#include <linux/intel_vsec.h>
+
+struct intel_tpmi_pm_feature;
+
+/**
+ * struct intel_tpmi_info - TPMI information for all IDs in an instance
+ * @tpmi_features: Pointer to a list of TPMI feature instances
+ * @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features
+ * @pfs_start: Start of PFS offset for the TPMI instances in this device
+ * @plat_info: Stores platform info which can be used by the client drivers
+ * @tpmi_control_mem: Memory mapped IO for getting control information
+ * @dbgfs_dir: debugfs entry pointer
+ * @resource: Array of feature_count MMIO resources, one per PFS entry
+ * @tpmi_dev: Device this TPMI instance is bound to. It backs the devm
+ * allocations, holds this structure as its driver data and
+ * the TPMI feature devices are created under it.
+ * @parent: Device this TPMI instance was enumerated from. It names
+ * the debugfs directory and becomes intel_vsec_device::dev
+ * of every TPMI feature device, so that the enumerating bus
+ * can find them again, for example during PCI error
+ * recovery.
+ *
+ * Stores the information for all TPMI devices of one TPMI instance.
+ */
+struct intel_tpmi_info {
+ struct intel_tpmi_pm_feature *tpmi_features;
+ int feature_count;
+ u64 pfs_start;
+ struct oobmsm_plat_info plat_info;
+ void __iomem *tpmi_control_mem;
+ struct dentry *dbgfs_dir;
+ struct resource *resource;
+ struct device *tpmi_dev;
+ struct device *parent;
+};
+
+int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev,
+ struct device *parent, struct resource *resource, int feature_count);
+void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info);
+
+#endif /* _TPMI_COMMON_H_ */
diff --git a/drivers/platform/x86/intel/tpmi_power_domains.c b/drivers/platform/x86/intel/tpmi_power_domains.c
index 833052dc34d2..d89b9894c42a 100644
--- a/drivers/platform/x86/intel/tpmi_power_domains.c
+++ b/drivers/platform/x86/intel/tpmi_power_domains.c
@@ -139,6 +139,13 @@ cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
cpumask_t *mask;
int index;
+ /*
+ * When built in, this stays callable after tpmi_init() failed
+ * before allocating the array.
+ */
+ if (!tpmi_power_domain_mask)
+ return NULL;
+
if (cpu_no >= num_possible_cpus())
return NULL;
@@ -156,6 +163,13 @@ EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
int tpmi_get_linux_die_id(int pkg_id, int domain_id)
{
+ /*
+ * When built in, this stays callable after tpmi_init() failed
+ * before allocating the map.
+ */
+ if (!domain_die_map)
+ return -ENODEV;
+
if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS)
return -EINVAL;
@@ -245,9 +259,11 @@ static int __init tpmi_init(void)
free_domain_map:
kfree(domain_die_map);
+ domain_die_map = NULL;
free_domain_mask:
kfree(tpmi_power_domain_mask);
+ tpmi_power_domain_mask = NULL;
return ret;
}
@@ -257,7 +273,9 @@ static void __exit tpmi_exit(void)
{
cpuhp_remove_state(tpmi_hp_state);
kfree(tpmi_power_domain_mask);
+ tpmi_power_domain_mask = NULL;
kfree(domain_die_map);
+ domain_die_map = NULL;
}
module_exit(tpmi_exit)
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
index 667f2c8b9594..3602b370177e 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
@@ -211,7 +211,7 @@ static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
struct uncore_data *data = &uncore_instances[i];
if (!data || !data->valid || !data->stored_uncore_data)
- return 0;
+ continue;
wrmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT,
data->stored_uncore_data);
@@ -257,6 +257,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, NULL),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_PANTHERLAKE_R, NULL),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL),
X86_MATCH_VFM(INTEL_NOVALAKE, NULL),
X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL),
diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c
index 5ab2215fdd7f..380d8231549f 100644
--- a/drivers/platform/x86/intel/vsec.c
+++ b/drivers/platform/x86/intel/vsec.c
@@ -51,6 +51,13 @@ struct vsec_priv {
unsigned long found_caps;
};
+static void intel_vsec_reset_state(struct vsec_priv *priv)
+{
+ memset(priv->suppliers, 0, sizeof(priv->suppliers));
+ memset(priv->state, 0, sizeof(priv->state));
+ priv->found_caps = 0;
+}
+
static const char *intel_vsec_name(enum intel_vsec_id id)
{
switch (id) {
@@ -641,7 +648,7 @@ static bool intel_vsec_get_features(struct pci_dev *pdev,
found = true;
if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
- intel_vsec_walk_header(&pdev->dev, info))
+ !intel_vsec_walk_header(&pdev->dev, info))
found = true;
return found;
@@ -669,8 +676,8 @@ static int intel_vsec_pci_init(struct pci_dev *pdev)
{
struct vsec_priv *priv = pci_get_drvdata(pdev);
const struct intel_vsec_platform_info *info = priv->info;
- int run_once = 0;
bool found_any = false;
+ bool run_once = false;
int num_caps;
num_caps = hweight_long(info->caps);
@@ -682,7 +689,7 @@ static int intel_vsec_pci_init(struct pci_dev *pdev)
if (!run_once) {
intel_vsec_skip_missing_dependencies(pdev);
- run_once = 1;
+ run_once = true;
}
}
@@ -717,15 +724,14 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
return intel_vsec_pci_init(pdev);
}
-int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
- struct intel_vsec_device *vsec_dev)
+int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct device *dev)
{
struct vsec_priv *priv;
- if (!dev_is_pci(vsec_dev->dev))
+ if (!dev_is_pci(dev))
return -ENODEV;
- priv = pci_get_drvdata(to_pci_dev(vsec_dev->dev));
+ priv = pci_get_drvdata(to_pci_dev(dev));
if (!priv)
return -EINVAL;
@@ -857,6 +863,7 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
{
struct intel_vsec_device *intel_vsec_dev;
pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT;
+ struct vsec_priv *priv = pci_get_drvdata(pdev);
unsigned long index;
dev_info(&pdev->dev, "Resetting PCI slot\n");
@@ -877,6 +884,13 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
devm_release_action(&pdev->dev, intel_vsec_remove_aux,
&intel_vsec_dev->auxdev);
}
+
+ /*
+ * intel_vsec_pci_init() reuses priv. Reset the registration state
+ * after tearing down old aux devices or the rewalk will skip them as
+ * already registered.
+ */
+ intel_vsec_reset_state(priv);
pci_restore_state(pdev);
intel_vsec_pci_init(pdev);
diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c
index 0153dd57838e..053fe0f58641 100644
--- a/drivers/platform/x86/intel/vsec_tpmi.c
+++ b/drivers/platform/x86/intel/vsec_tpmi.c
@@ -1,21 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Driver to enumerate TPMI features and create devices
+ * Enumerate TPMI features from a PCI VSEC structure
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.
*
- * The TPMI (Topology Aware Register and PM Capsule Interface) provides a
- * flexible, extendable and PCIe enumerable MMIO interface for PM features.
- *
- * For example Intel RAPL (Running Average Power Limit) provides a MMIO
- * interface using TPMI. This has advantage over traditional MSR
- * (Model Specific Register) interface, where a thread needs to be scheduled
- * on the target CPU to read or write. Also the RAPL features vary between
- * CPU models, and hence lot of model specific code. Here TPMI provides an
- * architectural interface by providing hierarchical tables and fields,
- * which will not need any model specific implementation.
- *
* The TPMI interface uses a PCI VSEC structure to expose the location of
* MMIO region.
*
@@ -29,823 +18,38 @@
* This TPMI driver will bind to the TPMI auxiliary device object created
* by the Intel VSEC driver.
*
- * The TPMI specification defines a PFS (PM Feature Structure) table.
- * This table is present in the TPMI MMIO region. The starting address
- * of PFS is derived from the tBIR (Bar Indicator Register) and "Address"
- * field from the VSEC header.
- *
- * Each TPMI PM feature has one entry in the PFS with a unique TPMI
- * ID and its access details. The TPMI driver creates device nodes
- * for the supported PM features.
- *
- * The names of the devices created by the TPMI driver start with the
- * "intel_vsec.tpmi-" prefix which is followed by a specific name of the
- * given PM feature (for example, "intel_vsec.tpmi-rapl.0").
- *
- * The device nodes are create by using interface "intel_vsec_add_aux()"
- * provided by the Intel VSEC driver.
+ * The starting address of the PFS table in the TPMI MMIO region is derived
+ * from the tBIR (Bar Indicator Register) and "Address" field from the VSEC
+ * header by the Intel VSEC driver, which passes the result on as the
+ * resources of the auxiliary device. Everything that happens from there on
+ * is enumeration independent and lives in tpmi_common.c.
*/
-#include <linux/align.h>
#include <linux/auxiliary_bus.h>
-#include <linux/bitfield.h>
-#include <linux/debugfs.h>
-#include <linux/cleanup.h>
-#include <linux/delay.h>
-#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
-#include <linux/io.h>
-#include <linux/iopoll.h>
#include <linux/module.h>
-#include <linux/notifier.h>
-#include <linux/pci.h>
-#include <linux/security.h>
-#include <linux/sizes.h>
-#include <linux/string_helpers.h>
-
-/**
- * struct intel_tpmi_pfs_entry - TPMI PM Feature Structure (PFS) entry
- * @tpmi_id: TPMI feature identifier (what the feature is and its data format).
- * @num_entries: Number of feature interface instances present in the PFS.
- * This represents the maximum number of Power domains in the SoC.
- * @entry_size: Interface instance entry size in 32-bit words.
- * @cap_offset: Offset from the PM_Features base address to the base of the PM VSEC
- * register bank in KB.
- * @attribute: Feature attribute: 0=BIOS. 1=OS. 2-3=Reserved.
- * @reserved: Bits for use in the future.
- *
- * Represents one TPMI feature entry data in the PFS retrieved as is
- * from the hardware.
- */
-struct intel_tpmi_pfs_entry {
- u64 tpmi_id:8;
- u64 num_entries:8;
- u64 entry_size:16;
- u64 cap_offset:16;
- u64 attribute:2;
- u64 reserved:14;
-} __packed;
-
-/**
- * struct intel_tpmi_pm_feature - TPMI PM Feature information for a TPMI ID
- * @pfs_header: PFS header retireved from the hardware.
- * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially
- * this offset = "Address" from VSEC header + PFS Capability
- * offset for this feature entry.
- * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device
- *
- * Represents TPMI instance information for one TPMI ID.
- */
-struct intel_tpmi_pm_feature {
- struct intel_tpmi_pfs_entry pfs_header;
- u64 vsec_offset;
- struct intel_vsec_device *vsec_dev;
-};
-
-/**
- * struct intel_tpmi_info - TPMI information for all IDs in an instance
- * @tpmi_features: Pointer to a list of TPMI feature instances
- * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device
- * @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features
- * @pfs_start: Start of PFS offset for the TPMI instances in this device
- * @plat_info: Stores platform info which can be used by the client drivers
- * @tpmi_control_mem: Memory mapped IO for getting control information
- * @dbgfs_dir: debugfs entry pointer
- *
- * Stores the information for all TPMI devices enumerated from a single PCI device.
- */
-struct intel_tpmi_info {
- struct intel_tpmi_pm_feature *tpmi_features;
- struct intel_vsec_device *vsec_dev;
- int feature_count;
- u64 pfs_start;
- struct oobmsm_plat_info plat_info;
- void __iomem *tpmi_control_mem;
- struct dentry *dbgfs_dir;
-};
-
-/**
- * struct tpmi_info_header - CPU package ID to PCI device mapping information
- * @fn: PCI function number
- * @dev: PCI device number
- * @bus: PCI bus number
- * @pkg: CPU Package id
- * @segment: PCI segment id
- * @partition: Package Partition id
- * @cdie_mask: Bitmap of compute dies in the current partition
- * @reserved: Reserved for future use
- * @lock: When set to 1 the register is locked and becomes read-only
- * until next reset. Not for use by the OS driver.
- *
- * The structure to read hardware provided mapping information.
- */
-struct tpmi_info_header {
- u64 fn:3;
- u64 dev:5;
- u64 bus:8;
- u64 pkg:8;
- u64 segment:8;
- u64 partition:2;
- u64 cdie_mask:16;
- u64 reserved:13;
- u64 lock:1;
-} __packed;
-
-/**
- * struct tpmi_feature_state - Structure to read hardware state of a feature
- * @enabled: Enable state of a feature, 1: enabled, 0: disabled
- * @reserved_1: Reserved for future use
- * @write_blocked: Writes are blocked means all write operations are ignored
- * @read_blocked: Reads are blocked means will read 0xFFs
- * @pcs_select: Interface used by out of band software, not used in OS
- * @reserved_2: Reserved for future use
- * @id: TPMI ID of the feature
- * @reserved_3: Reserved for future use
- * @locked: When set to 1, OS can't change this register.
- *
- * The structure is used to read hardware state of a TPMI feature. This
- * information is used for debug and restricting operations for this feature.
- */
-struct tpmi_feature_state {
- u32 enabled:1;
- u32 reserved_1:3;
- u32 write_blocked:1;
- u32 read_blocked:1;
- u32 pcs_select:1;
- u32 reserved_2:1;
- u32 id:8;
- u32 reserved_3:15;
- u32 locked:1;
-} __packed;
-
-/*
- * The size from hardware is in u32 units. This size is from a trusted hardware,
- * but better to verify for pre silicon platforms. Set size to 0, when invalid.
- */
-#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \
-({ \
- pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \
-})
-
-/* Used during auxbus device creation */
-static DEFINE_IDA(intel_vsec_tpmi_ida);
-
-static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list);
-
-int tpmi_register_notifier(struct notifier_block *nb)
-{
- return blocking_notifier_chain_register(&tpmi_notify_list, nb);
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI");
-
-int tpmi_unregister_notifier(struct notifier_block *nb)
-{
- return blocking_notifier_chain_unregister(&tpmi_notify_list, nb);
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI");
-
-struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
-{
- struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
-
- return vsec_dev->priv_data;
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_get_platform_data, "INTEL_TPMI");
-
-int tpmi_get_resource_count(struct auxiliary_device *auxdev)
-{
- struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
-
- if (vsec_dev)
- return vsec_dev->num_resources;
-
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_count, "INTEL_TPMI");
-
-struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index)
-{
- struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
-
- if (vsec_dev && index < vsec_dev->num_resources)
- return &vsec_dev->resource[index];
-
- return NULL;
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, "INTEL_TPMI");
-
-/* TPMI Control Interface */
-
-#define TPMI_CONTROL_STATUS_OFFSET 0x00
-#define TPMI_COMMAND_OFFSET 0x08
-#define TMPI_CONTROL_DATA_VAL_OFFSET 0x0c
-
-/*
- * Spec is calling for max 1 seconds to get ownership at the worst
- * case. Read at 10 ms timeouts and repeat up to 1 second.
- */
-#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC)
-#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC)
-
-#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC)
-#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC
-
-/* TPMI Control status register defines */
-
-#define TPMI_CONTROL_STATUS_RB BIT_ULL(0)
-
-#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4)
-#define TPMI_OWNER_NONE 0
-#define TPMI_OWNER_IN_BAND 1
-
-#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6)
-#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8)
-#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16)
-
-#define TPMI_CMD_PKT_LEN 2
-#define TPMI_CMD_STATUS_SUCCESS 0x40
-
-/* TPMI command data registers */
-#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0)
-#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40)
-
-/* Command to send via control interface */
-#define TPMI_CONTROL_GET_STATE_CMD 0x10
-
-#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40)
-
-#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16)
-
-/* Mutex to complete get feature status without interruption */
-static DEFINE_MUTEX(tpmi_dev_lock);
-
-static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner)
-{
- u64 control;
-
- return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
- control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control),
- TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US);
-}
-
-static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id,
- struct tpmi_feature_state *feature_state)
-{
- u64 control, data;
- int ret;
-
- if (!tpmi_info->tpmi_control_mem)
- return -EFAULT;
-
- mutex_lock(&tpmi_dev_lock);
-
- /* Wait for owner bit set to 0 (none) */
- ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE);
- if (ret)
- goto err_unlock;
-
- /* set command id to 0x10 for TPMI_GET_STATE */
- data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD);
-
- /* 32 bits for DATA offset and +8 for feature_id field */
- data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id);
-
- /* Write at command offset for qword access */
- writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
-
- /* Wait for owner bit set to in-band */
- ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND);
- if (ret)
- goto err_unlock;
-
- /* Set Run Busy and packet length of 2 dwords */
- control = TPMI_CONTROL_STATUS_RB;
- control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN);
-
- /* Write at status offset for qword access */
- writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
-
- /* Wait for Run Busy clear */
- ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
- control, !(control & TPMI_CONTROL_STATUS_RB),
- TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US);
- if (ret)
- goto done_proc;
-
- control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control);
- if (control != TPMI_CMD_STATUS_SUCCESS) {
- ret = -EBUSY;
- goto done_proc;
- }
-
- /* Response is ready */
- memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET,
- sizeof(*feature_state));
-
- ret = 0;
-
-done_proc:
- /* Set CPL "completion" bit */
- writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
-
-err_unlock:
- mutex_unlock(&tpmi_dev_lock);
-
- return ret;
-}
-
-int tpmi_get_feature_status(struct auxiliary_device *auxdev,
- int feature_id, bool *read_blocked, bool *write_blocked)
-{
- struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent);
- struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev);
- struct tpmi_feature_state feature_state;
- int ret;
-
- ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state);
- if (ret)
- return ret;
-
- *read_blocked = feature_state.read_blocked;
- *write_blocked = feature_state.write_blocked;
-
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI");
-
-struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev)
-{
- struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent);
- struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev);
-
- return tpmi_info->dbgfs_dir;
-}
-EXPORT_SYMBOL_NS_GPL(tpmi_get_debugfs_dir, "INTEL_TPMI");
-
-static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused)
-{
- struct intel_tpmi_info *tpmi_info = s->private;
- int locked, disabled, read_blocked, write_blocked;
- struct tpmi_feature_state feature_state;
- struct intel_tpmi_pm_feature *pfs;
- int ret, i;
-
-
- seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start);
- seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n");
- for (i = 0; i < tpmi_info->feature_count; ++i) {
- pfs = &tpmi_info->tpmi_features[i];
- ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
- if (ret) {
- locked = 'U';
- disabled = 'U';
- read_blocked = 'U';
- write_blocked = 'U';
- } else {
- disabled = feature_state.enabled ? 'N' : 'Y';
- locked = feature_state.locked ? 'Y' : 'N';
- read_blocked = feature_state.read_blocked ? 'Y' : 'N';
- write_blocked = feature_state.write_blocked ? 'Y' : 'N';
- }
- seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%016llx\t%c\t%c\t\t%c\t\t%c\n",
- pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries,
- pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset,
- pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled,
- read_blocked, write_blocked);
- }
-
- return 0;
-}
-DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg);
-
-#define MEM_DUMP_COLUMN_COUNT 8
-
-static int tpmi_mem_dump_show(struct seq_file *s, void *unused)
-{
- size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32);
- struct intel_tpmi_pm_feature *pfs = s->private;
- int count, ret = 0;
- void __iomem *mem;
- u32 size;
- u64 off;
- u8 *buffer;
-
- size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
- if (!size)
- return -EIO;
-
- buffer = kmalloc(size, GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
-
- off = pfs->vsec_offset;
-
- mutex_lock(&tpmi_dev_lock);
-
- for (count = 0; count < pfs->pfs_header.num_entries; ++count) {
- seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off);
-
- mem = ioremap(off, size);
- if (!mem) {
- ret = -ENOMEM;
- break;
- }
-
- memcpy_fromio(buffer, mem, size);
- seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size,
- false);
+#include "tpmi_common.h"
- iounmap(mem);
-
- off += size;
- }
-
- mutex_unlock(&tpmi_dev_lock);
-
- kfree(buffer);
-
- return ret;
-}
-DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump);
-
-static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos)
-{
- struct seq_file *m = file->private_data;
- struct intel_tpmi_pm_feature *pfs = m->private;
- u32 addr, value, punit, size;
- u32 num_elems;
- void __iomem *mem;
- int ret;
-
- size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
- if (!size)
- return -EIO;
-
- u32 *array __free(kfree) = NULL;
- ret = parse_int_array_user(userbuf, len, (int **)&array);
- if (ret < 0)
- return ret;
-
- num_elems = *array;
- if (num_elems != 3)
- return -EINVAL;
-
- punit = array[1];
- addr = array[2];
- value = array[3];
-
- if (!IS_ALIGNED(addr, sizeof(u32)))
- return -EINVAL;
-
- if (punit >= pfs->pfs_header.num_entries)
- return -EINVAL;
-
- if (addr >= size)
- return -EINVAL;
-
- guard(mutex)(&tpmi_dev_lock);
-
- mem = ioremap(pfs->vsec_offset + punit * size, size);
- if (!mem)
- return -ENOMEM;
-
- writel(value, mem + addr);
-
- iounmap(mem);
-
- return len;
-}
-
-static int mem_write_show(struct seq_file *s, void *unused)
-{
- return 0;
-}
-
-static int mem_write_open(struct inode *inode, struct file *file)
-{
- return single_open(file, mem_write_show, inode->i_private);
-}
-
-static const struct file_operations mem_write_ops = {
- .open = mem_write_open,
- .read = seq_read,
- .write = mem_write,
- .llseek = seq_lseek,
- .release = single_release,
-};
-
-#define tpmi_to_dev(info) ((info)->vsec_dev->dev)
-
-static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info)
-{
- char name[64];
- int i;
-
- snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_to_dev(tpmi_info)));
- tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL);
-
- debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops);
-
- for (i = 0; i < tpmi_info->feature_count; ++i) {
- struct intel_tpmi_pm_feature *pfs;
- struct dentry *dir;
-
- pfs = &tpmi_info->tpmi_features[i];
- snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id);
- dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir);
-
- debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops);
- debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops);
- }
-}
-
-static void tpmi_set_control_base(struct auxiliary_device *auxdev,
- struct intel_tpmi_info *tpmi_info,
- struct intel_tpmi_pm_feature *pfs)
-{
- void __iomem *mem;
- u32 size;
-
- size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
- if (!size)
- return;
-
- mem = devm_ioremap(&auxdev->dev, pfs->vsec_offset, size);
- if (!mem)
- return;
-
- /* mem is pointing to TPMI CONTROL base */
- tpmi_info->tpmi_control_mem = mem;
-}
-
-static const char *intel_tpmi_name(enum intel_tpmi_id id)
-{
- switch (id) {
- case TPMI_ID_RAPL:
- return "rapl";
- case TPMI_ID_PEM:
- return "pem";
- case TPMI_ID_UNCORE:
- return "uncore";
- case TPMI_ID_SST:
- return "sst";
- case TPMI_ID_PLR:
- return "plr";
- default:
- return NULL;
- }
-}
-
-/* String Length for tpmi-"feature_name(upto 8 bytes)" */
-#define TPMI_FEATURE_NAME_LEN 14
-
-static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
- struct intel_tpmi_pm_feature *pfs,
- u64 pfs_start)
-{
- struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev;
- char feature_id_name[TPMI_FEATURE_NAME_LEN];
- struct intel_vsec_device *feature_vsec_dev;
- struct tpmi_feature_state feature_state;
- struct resource *res, *tmp;
- const char *name;
- int i, ret;
-
- ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
- if (ret)
- return ret;
-
- /*
- * If not enabled, continue to look at other features in the PFS, so return -EOPNOTSUPP.
- * This will not cause failure of loading of this driver.
- */
- if (!feature_state.enabled)
- return -EOPNOTSUPP;
-
- name = intel_tpmi_name(pfs->pfs_header.tpmi_id);
- if (!name)
- return -EOPNOTSUPP;
-
- feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries);
- if (!feature_vsec_dev)
- return -ENOMEM;
-
- feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
- res = feature_vsec_dev->resource;
-
- snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name);
-
- for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) {
- u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32);
-
- tmp->start = pfs->vsec_offset + entry_size_bytes * i;
- tmp->end = tmp->start + entry_size_bytes - 1;
- tmp->flags = IORESOURCE_MEM;
- }
-
- feature_vsec_dev->dev = vsec_dev->dev;
- feature_vsec_dev->priv_data = &tpmi_info->plat_info;
- feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info);
- feature_vsec_dev->ida = &intel_vsec_tpmi_ida;
-
- /*
- * intel_vsec_add_aux() is resource managed, no explicit
- * delete is required on error or on module unload.
- * feature_vsec_dev and res memory are also freed as part of
- * device deletion.
- */
- return intel_vsec_add_aux(&vsec_dev->auxdev.dev,
- feature_vsec_dev, feature_id_name);
-}
-
-static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info)
-{
- struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev;
- int ret, i;
-
- for (i = 0; i < vsec_dev->num_resources; i++) {
- ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i],
- tpmi_info->pfs_start);
- /*
- * Fail, if the supported features fails to create device,
- * otherwise, continue. Even if one device failed to create,
- * fail the loading of driver. Since intel_vsec_add_aux()
- * is resource managed, no clean up is required for the
- * successfully created devices.
- */
- if (ret && ret != -EOPNOTSUPP)
- return ret;
- }
-
- return 0;
-}
-
-#define TPMI_INFO_BUS_INFO_OFFSET 0x08
-#define TPMI_INFO_MAJOR_VERSION 0x00
-#define TPMI_INFO_MINOR_VERSION 0x02
-
-static int tpmi_process_info(struct intel_tpmi_info *tpmi_info,
- struct intel_tpmi_pm_feature *pfs)
-{
- struct tpmi_info_header header;
- void __iomem *info_mem;
- u64 feature_header;
- int ret = 0;
-
- info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32));
- if (!info_mem)
- return -ENOMEM;
-
- feature_header = readq(info_mem);
- if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) {
- ret = -ENODEV;
- goto error_info_header;
- }
-
- memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header));
-
- tpmi_info->plat_info.package_id = header.pkg;
- tpmi_info->plat_info.bus_number = header.bus;
- tpmi_info->plat_info.device_number = header.dev;
- tpmi_info->plat_info.function_number = header.fn;
-
- if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) {
- tpmi_info->plat_info.cdie_mask = header.cdie_mask;
- tpmi_info->plat_info.partition = header.partition;
- tpmi_info->plat_info.segment = header.segment;
- }
-
-error_info_header:
- iounmap(info_mem);
-
- return ret;
-}
-
-static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size)
-{
- void __iomem *pfs_mem;
-
- pfs_mem = ioremap(start, size);
- if (!pfs_mem)
- return -ENOMEM;
-
- memcpy_fromio(&pfs->pfs_header, pfs_mem, sizeof(pfs->pfs_header));
-
- iounmap(pfs_mem);
-
- return 0;
-}
-
-#define TPMI_CAP_OFFSET_UNIT 1024
-
-static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
+static int tpmi_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
- struct pci_dev *pci_dev = to_pci_dev(vsec_dev->dev);
struct intel_tpmi_info *tpmi_info;
- u64 pfs_start = 0;
- int ret, i;
tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL);
if (!tpmi_info)
return -ENOMEM;
- tpmi_info->vsec_dev = vsec_dev;
- tpmi_info->feature_count = vsec_dev->num_resources;
- tpmi_info->plat_info.bus_number = pci_dev->bus->number;
-
- tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, vsec_dev->num_resources,
- sizeof(*tpmi_info->tpmi_features),
- GFP_KERNEL);
- if (!tpmi_info->tpmi_features)
- return -ENOMEM;
-
- for (i = 0; i < vsec_dev->num_resources; i++) {
- struct intel_tpmi_pm_feature *pfs;
- struct resource *res;
- u64 res_start;
- int size, ret;
-
- pfs = &tpmi_info->tpmi_features[i];
- pfs->vsec_dev = vsec_dev;
-
- res = &vsec_dev->resource[i];
- if (!res)
- continue;
-
- res_start = res->start;
- size = resource_size(res);
- if (size < 0)
- continue;
-
- ret = tpmi_fetch_pfs_header(pfs, res_start, size);
- if (ret)
- continue;
-
- if (!pfs_start)
- pfs_start = res_start;
-
- pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset * TPMI_CAP_OFFSET_UNIT;
-
- /*
- * Process TPMI_INFO to get PCI device to CPU package ID.
- * Device nodes for TPMI features are not created in this
- * for loop. So, the mapping information will be available
- * when actual device nodes created outside this
- * loop via tpmi_create_devices().
- */
- if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) {
- ret = tpmi_process_info(tpmi_info, pfs);
- if (ret)
- return ret;
-
- ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev);
- if (ret)
- return ret;
- }
-
- if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)
- tpmi_set_control_base(auxdev, tpmi_info, pfs);
- }
-
- tpmi_info->pfs_start = pfs_start;
-
- auxiliary_set_drvdata(auxdev, tpmi_info);
-
- /*
- * Allow debugfs when security policy allows. Everything this debugfs
- * interface provides, can also be done via /dev/mem access. If
- * /dev/mem interface is locked, don't allow debugfs to present any
- * information. Also check for CAP_SYS_RAWIO as /dev/mem interface.
- */
- if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO))
- tpmi_dbgfs_register(tpmi_info);
-
- ret = tpmi_create_devices(tpmi_info);
- if (ret) {
- debugfs_remove_recursive(tpmi_info->dbgfs_dir);
- return ret;
- }
-
- blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, auxdev);
-
- return 0;
-}
-
-static int tpmi_probe(struct auxiliary_device *auxdev,
- const struct auxiliary_device_id *id)
-{
- return intel_vsec_tpmi_init(auxdev);
+ return intel_tpmi_init(tpmi_info, &auxdev->dev, vsec_dev->dev,
+ vsec_dev->resource, vsec_dev->num_resources);
}
static void tpmi_remove(struct auxiliary_device *auxdev)
{
struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev);
- blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, auxdev);
-
- debugfs_remove_recursive(tpmi_info->dbgfs_dir);
+ intel_tpmi_deinit(tpmi_info);
}
static const struct auxiliary_device_id tpmi_id_table[] = {
@@ -862,6 +66,6 @@ static struct auxiliary_driver tpmi_aux_driver = {
module_auxiliary_driver(tpmi_aux_driver);
-MODULE_IMPORT_NS("INTEL_VSEC");
+MODULE_IMPORT_NS("INTEL_TPMI");
MODULE_DESCRIPTION("Intel TPMI enumeration module");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
index 62c9c7f1842b..7e07cfab227c 100644
--- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c
+++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
@@ -61,7 +61,7 @@ static ssize_t firmware_update_request_show(struct device *dev,
if (ret)
return ret;
- return sprintf(buf, "%d\n", val);
+ return sysfs_emit(buf, "%u\n", val);
}
static ssize_t firmware_update_request_store(struct device *dev,
@@ -93,18 +93,6 @@ static struct attribute *firmware_update_attrs[] = {
};
ATTRIBUTE_GROUPS(firmware_update);
-static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev,
- const void *context)
-{
- dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n");
- return 0;
-}
-
-static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev)
-{
- dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n");
-}
-
static const struct wmi_device_id intel_wmi_sbl_id_table[] = {
{ .guid_string = INTEL_WMI_SBL_GUID },
{}
@@ -116,8 +104,6 @@ static struct wmi_driver intel_wmi_sbl_fw_update_driver = {
.name = "intel-wmi-sbl-fw-update",
.dev_groups = firmware_update_groups,
},
- .probe = intel_wmi_sbl_fw_update_probe,
- .remove = intel_wmi_sbl_fw_update_remove,
.id_table = intel_wmi_sbl_id_table,
.no_singleton = true,
};
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 8213524504ee..fcb5ed7035f5 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -1358,13 +1358,29 @@ static const struct key_entry ideapad_keymap[] = {
{ KE_KEY, 0x44 | IDEAPAD_WMI_KEY, { KEY_PROG1 } },
/* Eye */
{ KE_KEY, 0x45 | IDEAPAD_WMI_KEY, { KEY_PROG3 } },
+ /* Lenovo Smart Connect */
+ { KE_KEY, 0x48 | IDEAPAD_WMI_KEY, { KEY_LINK_PHONE } },
/* Performance toggle also Fn+Q, handled inside ideapad_wmi_notify() */
{ KE_KEY, 0x3d | IDEAPAD_WMI_KEY, { KEY_PROG4 } },
+ /* Mode, opens a profile chooser; the Fn+Q button cycles the profile */
+ { KE_KEY, 0x46 | IDEAPAD_WMI_KEY, { KEY_PROG4 } },
/* shift + prtsc */
{ KE_KEY, 0x2d | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } },
{ KE_KEY, 0x29 | IDEAPAD_WMI_KEY, { KEY_TOUCHPAD_TOGGLE } },
{ KE_KEY, 0x2a | IDEAPAD_WMI_KEY, { KEY_ROOT_MENU } },
+ /*
+ * WMI switches
+ *
+ * Some devices come with physical switches. Whenever a switch is
+ * toggled, an WMI event is emitted to inform the software about the
+ * switch's latest state.
+ */
+
+ /* Camera switch */
+ { KE_SW, 0x0c | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 0 } },
+ { KE_SW, 0x0d | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 1 } },
+
{ KE_END },
};
diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c
index ebfc6182986e..2c45c98ec0ec 100644
--- a/drivers/platform/x86/lenovo/think-lmi.c
+++ b/drivers/platform/x86/lenovo/think-lmi.c
@@ -249,7 +249,7 @@ static int tlmi_errstr_to_err(const char *errstr)
{
int i;
- for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) {
+ for (i = 0; i < ARRAY_SIZE(tlmi_errs); i++) {
if (!strcmp(tlmi_errs[i].err_str, errstr))
return tlmi_errs[i].err_code;
}
@@ -487,16 +487,16 @@ static ssize_t new_password_store(struct kobject *kobj,
/* Special handling required for HDD and NVMe passwords */
if (setting == tlmi_priv.pwd_hdd) {
if (setting->level == TLMI_LEVEL_USER)
- sprintf(pwd_type, "uhdp%d", setting->index);
+ scnprintf(pwd_type, sizeof(pwd_type), "uhdp%d", setting->index);
else
- sprintf(pwd_type, "mhdp%d", setting->index);
+ scnprintf(pwd_type, sizeof(pwd_type), "mhdp%d", setting->index);
} else if (setting == tlmi_priv.pwd_nvme) {
if (setting->level == TLMI_LEVEL_USER)
- sprintf(pwd_type, "udrp%d", setting->index);
+ scnprintf(pwd_type, sizeof(pwd_type), "udrp%d", setting->index);
else
- sprintf(pwd_type, "adrp%d", setting->index);
+ scnprintf(pwd_type, sizeof(pwd_type), "adrp%d", setting->index);
} else {
- sprintf(pwd_type, "%s", setting->pwd_type);
+ scnprintf(pwd_type, sizeof(pwd_type), "%s", setting->pwd_type);
}
ret = tlmi_opcode_setting("WmiOpcodePasswordType", pwd_type);
@@ -1281,7 +1281,7 @@ static const struct kobj_type tlmi_pwd_setting_ktype = {
static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
- return sprintf(buf, "%d\n", tlmi_priv.pending_changes);
+ return sysfs_emit(buf, "%d\n", tlmi_priv.pending_changes);
}
static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index 1661f070c571..4f81855ad721 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -222,8 +222,10 @@ enum tpacpi_hkey_event_t {
TP_HKEY_EV_LID_OPEN = 0x5002, /* laptop lid opened */
TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */
TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */
- TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* X1 Yoga (2016):
- * enter/leave tablet mode
+ TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* posture change event:
+ * X1 Yoga (2016): enter/leave tablet mode
+ * X1 Fold 16 Gen 1: keyboard
+ * attachment state changed
*/
TP_HKEY_EV_PEN_INSERTED = 0x500b, /* tablet pen inserted */
TP_HKEY_EV_PEN_REMOVED = 0x500c, /* tablet pen removed */
@@ -379,6 +381,7 @@ static struct {
u32 kbd_lang:1;
u32 trackpoint_doubletap_enable:1;
u32 usbc_security_supported:1;
+ u32 has_keyboard_attached_on_screen:1;
bool usbc_security_enabled;
struct quirk_entry *quirks;
} tp_features;
@@ -456,9 +459,7 @@ do { \
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
#define vdbg_printk dbg_printk
-static const char *str_supported(int is_supported);
#else
-static inline const char *str_supported(int is_supported) { return ""; }
#define vdbg_printk(a_dbg_level, format, arg...) \
do { if (0) no_printk(format, ##arg); } while (0)
#endif
@@ -890,7 +891,6 @@ static ssize_t dispatch_proc_write(struct file *file,
size_t count, loff_t *pos)
{
struct ibm_struct *ibm = pde_data(file_inode(file));
- char *kernbuf;
int ret;
if (!ibm || !ibm->write)
@@ -898,16 +898,15 @@ static ssize_t dispatch_proc_write(struct file *file,
if (count > PAGE_SIZE - 1)
return -EINVAL;
- kernbuf = memdup_user_nul(userbuf, count);
+ char *kernbuf __free(kfree) = memdup_user_nul(userbuf, count);
if (IS_ERR(kernbuf))
return PTR_ERR(kernbuf);
- ret = ibm->write(kernbuf);
- if (ret == 0)
- ret = count;
- kfree(kernbuf);
+ ret = ibm->write(kernbuf);
+ if (ret)
+ return ret;
- return ret;
+ return count;
}
static const struct proc_ops dispatch_proc_ops = {
@@ -1886,6 +1885,7 @@ static u32 hotkey_reserved_mask; /* events better left disabled */
static u32 hotkey_driver_mask; /* events needed by the driver */
static u32 hotkey_user_mask; /* events visible to userspace */
static u32 hotkey_acpi_mask; /* events enabled in firmware */
+static bool hotkey_input_enabled;
static bool tpacpi_driver_event(const unsigned int hkey_event);
static void hotkey_poll_setup(const bool may_warn);
@@ -2107,7 +2107,7 @@ static int hotkey_mask_set(u32 mask)
}
/*
- * We *must* make an inconditional call to hotkey_mask_get to
+ * We *must* make an unconditional call to hotkey_mask_get to
* refresh hotkey_acpi_mask and update hotkey_user_mask
*
* Take the opportunity to also log when we cannot _enable_
@@ -2169,7 +2169,7 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask)
return 0;
}
- mutex_lock(&hotkey_mutex);
+ guard(mutex)(&hotkey_mutex);
HOTKEY_CONFIG_CRITICAL_START
hotkey_driver_mask = mask;
@@ -2182,8 +2182,6 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask)
~hotkey_source_mask);
hotkey_poll_setup(true);
- mutex_unlock(&hotkey_mutex);
-
return rc;
}
@@ -2207,15 +2205,12 @@ static void tpacpi_input_send_tabletsw(void)
{
int state;
- if (tp_features.hotkey_tablet &&
- !hotkey_get_tablet_mode(&state)) {
- mutex_lock(&tpacpi_inputdev_send_mutex);
+ if (tp_features.hotkey_tablet && !hotkey_get_tablet_mode(&state)) {
+ guard(mutex)(&tpacpi_inputdev_send_mutex);
input_report_switch(tpacpi_inputdev,
SW_TABLET_MODE, !!state);
input_sync(tpacpi_inputdev);
-
- mutex_unlock(&tpacpi_inputdev_send_mutex);
}
}
@@ -2240,7 +2235,6 @@ static int get_camera_shutter(void)
static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev)
{
- bool known_ev;
u32 scancode;
if (tpacpi_driver_event(hkey))
@@ -2283,11 +2277,8 @@ static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev)
scancode = hkey;
}
- mutex_lock(&tpacpi_inputdev_send_mutex);
- known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true);
- mutex_unlock(&tpacpi_inputdev_send_mutex);
-
- return known_ev;
+ guard(mutex)(&tpacpi_inputdev_send_mutex);
+ return sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true);
}
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
@@ -2553,8 +2544,7 @@ static void hotkey_poll_setup(const bool may_warn)
lockdep_assert_held(&hotkey_mutex);
if (hotkey_poll_freq > 0 &&
- (poll_driver_mask ||
- (poll_user_mask && tpacpi_inputdev->users > 0))) {
+ (poll_driver_mask || (poll_user_mask && hotkey_input_enabled))) {
if (!tpacpi_hotkey_task) {
tpacpi_hotkey_task = kthread_run(hotkey_kthread,
NULL, TPACPI_NVRAM_KTHREAD_NAME);
@@ -2575,9 +2565,8 @@ static void hotkey_poll_setup(const bool may_warn)
static void hotkey_poll_setup_safe(const bool may_warn)
{
- mutex_lock(&hotkey_mutex);
+ guard(mutex)(&hotkey_mutex);
hotkey_poll_setup(may_warn);
- mutex_unlock(&hotkey_mutex);
}
static void hotkey_poll_set_freq(unsigned int freq)
@@ -2610,7 +2599,10 @@ static int hotkey_inputdev_open(struct input_dev *dev)
switch (tpacpi_lifecycle) {
case TPACPI_LIFE_INIT:
case TPACPI_LIFE_RUNNING:
- hotkey_poll_setup_safe(false);
+ scoped_guard(mutex, &hotkey_mutex) {
+ hotkey_input_enabled = true;
+ hotkey_poll_setup(false);
+ }
return 0;
case TPACPI_LIFE_EXITING:
return -EBUSY;
@@ -2623,10 +2615,14 @@ static int hotkey_inputdev_open(struct input_dev *dev)
static void hotkey_inputdev_close(struct input_dev *dev)
{
+ guard(mutex)(&hotkey_mutex);
+
+ hotkey_input_enabled = false;
+
/* disable hotkey polling when possible */
if (tpacpi_lifecycle != TPACPI_LIFE_EXITING &&
!(hotkey_source_mask & hotkey_driver_mask))
- hotkey_poll_setup_safe(false);
+ hotkey_poll_setup(false);
}
/* sysfs hotkey enable ------------------------------------------------- */
@@ -2684,8 +2680,10 @@ static ssize_t hotkey_mask_store(struct device *dev,
if (parse_strtoul(buf, 0xffffffffUL, &t))
return -EINVAL;
- if (mutex_lock_killable(&hotkey_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&hotkey_mutex);
+ res = ACQUIRE_ERR(mutex_kill, &guard);
+ if (res)
+ return res;
res = hotkey_user_mask_set(t);
@@ -2693,8 +2691,6 @@ static ssize_t hotkey_mask_store(struct device *dev,
hotkey_poll_setup(true);
#endif
- mutex_unlock(&hotkey_mutex);
-
tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t);
return (res) ? res : count;
@@ -2780,8 +2776,10 @@ static ssize_t hotkey_source_mask_store(struct device *dev,
((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
return -EINVAL;
- if (mutex_lock_killable(&hotkey_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&hotkey_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
HOTKEY_CONFIG_CRITICAL_START
hotkey_source_mask = t;
@@ -2795,8 +2793,6 @@ static ssize_t hotkey_source_mask_store(struct device *dev,
r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask)
& ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK;
- mutex_unlock(&hotkey_mutex);
-
if (rc < 0)
pr_err("hotkey_source_mask: failed to update the firmware event mask!\n");
@@ -2824,18 +2820,19 @@ static ssize_t hotkey_poll_freq_store(struct device *dev,
const char *buf, size_t count)
{
unsigned long t;
+ int err;
if (parse_strtoul(buf, 25, &t))
return -EINVAL;
- if (mutex_lock_killable(&hotkey_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&hotkey_mutex);
+ err = ACQUIRE_ERR(mutex_kill, &guard);
+ if (err)
+ return err;
hotkey_poll_set_freq(t);
hotkey_poll_setup(true);
- mutex_unlock(&hotkey_mutex);
-
tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t);
return count;
@@ -2893,6 +2890,63 @@ static void hotkey_tablet_mode_notify_change(void)
"hotkey_tablet_mode");
}
+static bool keyboard_attached_on_screen;
+static bool keyboard_attached_on_screen_initialized;
+
+static int x1_fold_keyboard_attached_on_screen_get(bool *attached)
+{
+ int state;
+
+ if (!tp_features.has_keyboard_attached_on_screen)
+ return -ENODEV;
+
+ if (!acpi_evalf(NULL, &state, "\\_SB.DEVD.GDST", "d"))
+ return -EIO;
+
+ *attached = state != 0;
+ return 0;
+}
+
+static ssize_t keyboard_attached_on_screen_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ bool attached;
+ int res;
+
+ res = x1_fold_keyboard_attached_on_screen_get(&attached);
+ if (res)
+ return res;
+
+ return sysfs_emit(buf, "%d\n", attached);
+}
+
+static DEVICE_ATTR_RO(keyboard_attached_on_screen);
+
+static void keyboard_attached_on_screen_notify_change(void)
+{
+ if (tp_features.has_keyboard_attached_on_screen)
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+ "keyboard_attached_on_screen");
+}
+
+static bool keyboard_attached_on_screen_update(void)
+{
+ bool attached;
+
+ if (x1_fold_keyboard_attached_on_screen_get(&attached))
+ return false;
+
+ if (keyboard_attached_on_screen_initialized &&
+ keyboard_attached_on_screen == attached)
+ return false;
+
+ keyboard_attached_on_screen = attached;
+ keyboard_attached_on_screen_initialized = true;
+
+ return true;
+}
+
/* sysfs wakeup reason (pollable) -------------------------------------- */
static ssize_t hotkey_wakeup_reason_show(struct device *dev,
struct device_attribute *attr,
@@ -3022,6 +3076,7 @@ static struct attribute *hotkey_attributes[] = {
&dev_attr_hotkey_adaptive_all_mask.attr,
&dev_attr_hotkey_recommended_mask.attr,
&dev_attr_hotkey_tablet_mode.attr,
+ &dev_attr_keyboard_attached_on_screen.attr,
&dev_attr_hotkey_radio_sw.attr,
&dev_attr_doubletap_enable.attr,
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
@@ -3037,6 +3092,9 @@ static umode_t hotkey_attr_is_visible(struct kobject *kobj,
if (attr == &dev_attr_hotkey_tablet_mode.attr) {
if (!tp_features.hotkey_tablet)
return 0;
+ } else if (attr == &dev_attr_keyboard_attached_on_screen.attr) {
+ if (!tp_features.has_keyboard_attached_on_screen)
+ return 0;
} else if (attr == &dev_attr_hotkey_radio_sw.attr) {
if (!tp_features.hotkey_wlsw)
return 0;
@@ -3080,13 +3138,11 @@ static void tpacpi_send_radiosw_update(void)
/* Issue rfkill input event for WLSW switch */
if (!(wlsw < 0)) {
- mutex_lock(&tpacpi_inputdev_send_mutex);
+ guard(mutex)(&tpacpi_inputdev_send_mutex);
input_report_switch(tpacpi_inputdev,
SW_RFKILL_ALL, (wlsw > 0));
input_sync(tpacpi_inputdev);
-
- mutex_unlock(&tpacpi_inputdev_send_mutex);
}
/*
@@ -3098,7 +3154,7 @@ static void tpacpi_send_radiosw_update(void)
static void hotkey_exit(void)
{
- mutex_lock(&hotkey_mutex);
+ guard(mutex)(&hotkey_mutex);
hotkey_poll_stop_sync();
dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY,
"restoring original HKEY status and mask\n");
@@ -3108,8 +3164,6 @@ static void hotkey_exit(void)
hotkey_mask_set(hotkey_orig_mask)) |
hotkey_status_set(false)) != 0)
pr_err("failed to restore hot key mask to BIOS defaults\n");
-
- mutex_unlock(&hotkey_mutex);
}
/*
@@ -3338,7 +3392,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
"hotkeys are %s\n",
- str_supported(tp_features.hotkey));
+ str_supported_unsupported(tp_features.hotkey));
if (!tp_features.hotkey)
return -ENODEV;
@@ -3415,7 +3469,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
"hotkey masks are %s\n",
- str_supported(tp_features.hotkey_mask));
+ str_supported_unsupported(tp_features.hotkey_mask));
/* Init hotkey_all_mask if not initialized yet */
if (!tp_features.hotkey_mask && !hotkey_all_mask &&
@@ -3426,11 +3480,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
if (tp_features.hotkey_mask) {
/* hotkey_source_mask *must* be zero for
* the first hotkey_mask_get to return hotkey_orig_mask */
- mutex_lock(&hotkey_mutex);
- res = hotkey_mask_get();
- mutex_unlock(&hotkey_mutex);
- if (res)
- return res;
+ scoped_guard(mutex, &hotkey_mutex) {
+ res = hotkey_mask_get();
+ if (res)
+ return res;
+ }
hotkey_orig_mask = hotkey_acpi_mask;
} else {
@@ -3453,6 +3507,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
}
tabletsw_state = hotkey_init_tablet_mode();
+ keyboard_attached_on_screen_update();
/* Set up key map */
keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
@@ -3529,11 +3584,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
hotkey_exit();
return res;
}
- mutex_lock(&hotkey_mutex);
- res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
- | hotkey_driver_mask)
- & ~hotkey_source_mask);
- mutex_unlock(&hotkey_mutex);
+ scoped_guard(mutex, &hotkey_mutex) {
+ res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
+ | hotkey_driver_mask)
+ & ~hotkey_source_mask);
+ }
if (res < 0 && res != -ENXIO) {
hotkey_exit();
return res;
@@ -3720,7 +3775,7 @@ static bool hotkey_notify_dockevent(const u32 hkey, bool *send_acpi_ev)
return true;
/*
- * Deliberately ignore attaching and detaching the keybord cover to avoid
+ * Deliberately ignore attaching and detaching the keyboard cover to avoid
* duplicates from intel-vbtn, which already emits SW_TABLET_MODE events
* to userspace.
*
@@ -3833,6 +3888,8 @@ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev)
case TP_HKEY_EV_TABLET_CHANGED:
tpacpi_input_send_tabletsw();
hotkey_tablet_mode_notify_change();
+ if (keyboard_attached_on_screen_update())
+ keyboard_attached_on_screen_notify_change();
*send_acpi_ev = false;
return true;
@@ -3980,20 +4037,22 @@ static void hotkey_resume(void)
{
tpacpi_disable_brightness_delay();
- mutex_lock(&hotkey_mutex);
- if (hotkey_status_set(true) < 0 ||
- hotkey_mask_set(hotkey_acpi_mask) < 0)
- pr_err("error while attempting to reset the event firmware interface\n");
- mutex_unlock(&hotkey_mutex);
+ scoped_guard(mutex, &hotkey_mutex) {
+ if (hotkey_status_set(true) < 0 ||
+ hotkey_mask_set(hotkey_acpi_mask) < 0)
+ pr_err("error while attempting to reset the event firmware interface\n");
+ }
tpacpi_send_radiosw_update();
tpacpi_input_send_tabletsw();
hotkey_tablet_mode_notify_change();
+ keyboard_attached_on_screen_update();
+ keyboard_attached_on_screen_notify_change();
hotkey_wakeup_reason_notify_change();
hotkey_wakeup_hotunplug_complete_notify_change();
hotkey_poll_setup_safe(false);
- /* restore previous mode of adapive keyboard of X1 Carbon */
+ /* restore previous mode of adaptive keyboard of X1 Carbon */
if (tp_features.has_adaptive_kbd) {
if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd",
adaptive_keyboard_prev_mode)) {
@@ -4012,12 +4071,13 @@ static int hotkey_read(struct seq_file *m)
return 0;
}
- if (mutex_lock_killable(&hotkey_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&hotkey_mutex);
+ res = ACQUIRE_ERR(mutex_kill, &guard);
+ if (res)
+ return res;
res = hotkey_status_get(&status);
if (!res)
res = hotkey_mask_get();
- mutex_unlock(&hotkey_mutex);
if (res)
return res;
@@ -4050,8 +4110,10 @@ static int hotkey_write(char *buf)
if (!tp_features.hotkey)
return -ENODEV;
- if (mutex_lock_killable(&hotkey_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&hotkey_mutex);
+ res = ACQUIRE_ERR(mutex_kill, &guard);
+ if (res)
+ return res;
mask = hotkey_user_mask;
@@ -4070,8 +4132,7 @@ static int hotkey_write(char *buf)
} else if (sscanf(cmd, "%x", &mask) == 1) {
/* mask set */
} else {
- res = -EINVAL;
- goto errexit;
+ return -EINVAL;
}
}
@@ -4081,8 +4142,6 @@ static int hotkey_write(char *buf)
res = hotkey_user_mask_set(mask);
}
-errexit:
- mutex_unlock(&hotkey_mutex);
return res;
}
@@ -4287,6 +4346,17 @@ static const struct dmi_system_id fwbug_list[] __initconst = {
{}
};
+static const struct dmi_system_id keyboard_attached_on_screen_list[] __initconst = {
+ {
+ .ident = "ThinkPad X1 Fold 16 Gen 1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Fold 16 Gen 1"),
+ },
+ },
+ {}
+};
+
static const struct pci_device_id fwbug_cards_ids[] __initconst = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24F3) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24FD) },
@@ -4327,7 +4397,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
"bluetooth is %s, status 0x%02x\n",
- str_supported(tp_features.bluetooth),
+ str_supported_unsupported(tp_features.bluetooth),
status);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
@@ -4506,7 +4576,7 @@ static int __init wan_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
"wan is %s, status 0x%02x\n",
- str_supported(tp_features.wan),
+ str_supported_unsupported(tp_features.wan),
status);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
@@ -4634,7 +4704,7 @@ static int __init uwb_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
"uwb is %s, status 0x%02x\n",
- str_supported(tp_features.uwb),
+ str_supported_unsupported(tp_features.uwb),
status);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
@@ -4742,7 +4812,7 @@ static int __init video_init(struct ibm_init_struct *iibm)
video_supported = TPACPI_VIDEO_NEW;
vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
- str_supported(video_supported != TPACPI_VIDEO_NONE),
+ str_supported_unsupported(video_supported != TPACPI_VIDEO_NONE),
video_supported);
return (video_supported != TPACPI_VIDEO_NONE) ? 0 : -ENODEV;
@@ -5037,21 +5107,16 @@ static DEFINE_MUTEX(kbdlight_mutex);
static int kbdlight_set_level(int level)
{
- int ret = 0;
-
if (!hkey_handle)
return -ENXIO;
- mutex_lock(&kbdlight_mutex);
+ guard(mutex)(&kbdlight_mutex);
if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level))
- ret = -EIO;
- else
- kbdlight_brightness = level;
-
- mutex_unlock(&kbdlight_mutex);
+ return -EIO;
- return ret;
+ kbdlight_brightness = level;
+ return 0;
}
static int kbdlight_get_level(void)
@@ -5340,8 +5405,8 @@ static int __init light_init(struct ibm_init_struct *iibm)
acpi_evalf(ec_handle, NULL, "KBLT", "qv");
vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n",
- str_supported(tp_features.light),
- str_supported(tp_features.light_status));
+ str_supported_unsupported(tp_features.light),
+ str_supported_unsupported(tp_features.light_status));
if (!tp_features.light)
return -ENODEV;
@@ -5458,7 +5523,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm)
TPACPI_ACPIHANDLE_INIT(cmos);
vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
- str_supported(cmos_handle != NULL));
+ str_supported_unsupported(cmos_handle));
return cmos_handle ? 0 : -ENODEV;
}
@@ -5803,7 +5868,8 @@ static int __init led_init(struct ibm_init_struct *iibm)
}
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
- str_supported(led_supported), led_supported);
+ str_supported_unsupported(led_supported != TPACPI_LED_NONE),
+ led_supported);
if (led_supported == TPACPI_LED_NONE)
return -ENODEV;
@@ -5924,7 +5990,7 @@ static int __init beep_init(struct ibm_init_struct *iibm)
TPACPI_ACPIHANDLE_INIT(beep);
vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
- str_supported(beep_handle != NULL));
+ str_supported_unsupported(beep_handle));
quirks = tpacpi_check_quirks(beep_quirk_table,
ARRAY_SIZE(beep_quirk_table));
@@ -5936,12 +6002,8 @@ static int __init beep_init(struct ibm_init_struct *iibm)
static int beep_read(struct seq_file *m)
{
- if (!beep_handle)
- seq_puts(m, "status:\t\tnot supported\n");
- else {
- seq_puts(m, "status:\t\tsupported\n");
- seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
- }
+ seq_puts(m, "status:\t\tsupported\n");
+ seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
return 0;
}
@@ -6374,7 +6436,7 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
thermal_read_mode = thermal_read_mode_check();
vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
- str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
+ str_supported_unsupported(thermal_read_mode != TPACPI_THERMAL_NONE),
thermal_read_mode);
return thermal_read_mode != TPACPI_THERMAL_NONE ? 0 : -ENODEV;
@@ -6482,11 +6544,12 @@ static void tpacpi_brightness_checkpoint_nvram(void)
vdbg_printk(TPACPI_DBG_BRGHT,
"trying to checkpoint backlight level to NVRAM...\n");
- if (mutex_lock_killable(&brightness_mutex) < 0)
+ ACQUIRE(mutex_kill, guard)(&brightness_mutex);
+ if (ACQUIRE_ERR(mutex_kill, &guard))
return;
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
- goto unlock;
+ return;
lec &= TP_EC_BACKLIGHT_LVLMSK;
b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
@@ -6504,9 +6567,6 @@ static void tpacpi_brightness_checkpoint_nvram(void)
vdbg_printk(TPACPI_DBG_BRGHT,
"NVRAM backlight level already is %u (0x%02x)\n",
(unsigned int) lec, (unsigned int) b_nvram);
-
-unlock:
- mutex_unlock(&brightness_mutex);
}
@@ -6584,8 +6644,9 @@ static int brightness_set(unsigned int value)
vdbg_printk(TPACPI_DBG_BRGHT,
"set backlight level to %d\n", value);
- res = mutex_lock_killable(&brightness_mutex);
- if (res < 0)
+ ACQUIRE(mutex_kill, guard)(&brightness_mutex);
+ res = ACQUIRE_ERR(mutex_kill, &guard);
+ if (res)
return res;
switch (brightness_mode) {
@@ -6600,7 +6661,6 @@ static int brightness_set(unsigned int value)
res = -ENXIO;
}
- mutex_unlock(&brightness_mutex);
return res;
}
@@ -6623,14 +6683,11 @@ static int brightness_get(struct backlight_device *bd)
{
int status, res;
- res = mutex_lock_killable(&brightness_mutex);
- if (res < 0)
+ ACQUIRE(mutex_kill, guard)(&brightness_mutex);
+ if (ACQUIRE_ERR(mutex_kill, &guard))
return 0;
res = tpacpi_brightness_get_raw(&status);
-
- mutex_unlock(&brightness_mutex);
-
if (res < 0)
return 0;
@@ -6653,26 +6710,21 @@ static const struct backlight_ops ibm_backlight_data = {
static int __init tpacpi_evaluate_bcl(struct acpi_device *adev, void *not_used)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
- union acpi_object *obj;
acpi_status status;
- int rc;
status = acpi_evaluate_object(adev->handle, "_BCL", NULL, &buffer);
if (ACPI_FAILURE(status))
return 0;
- obj = buffer.pointer;
+ union acpi_object *obj __free(kfree) = buffer.pointer;
if (!obj || obj->type != ACPI_TYPE_PACKAGE) {
acpi_handle_info(adev->handle,
"Unknown _BCL data, please report this to %s\n",
TPACPI_MAIL);
- rc = 0;
- } else {
- rc = obj->package.count;
+ return 0;
}
- kfree(obj);
- return rc;
+ return obj->package.count;
}
/*
@@ -7069,7 +7121,7 @@ static bool software_mute_active;
static int software_mute_orig_mode;
/*
- * Used to syncronize writers to TP_EC_AUDIO and
+ * Used to synchronize writers to TP_EC_AUDIO and
* TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
*/
static struct mutex volume_mutex;
@@ -7095,11 +7147,12 @@ static void tpacpi_volume_checkpoint_nvram(void)
else
ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
- if (mutex_lock_killable(&volume_mutex) < 0)
+ ACQUIRE(mutex_kill, guard)(&volume_mutex);
+ if (ACQUIRE_ERR(mutex_kill, &guard))
return;
if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
- goto unlock;
+ return;
lec &= ec_mask;
b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
@@ -7116,9 +7169,6 @@ static void tpacpi_volume_checkpoint_nvram(void)
"NVRAM mixer status already is 0x%02x (0x%02x)\n",
(unsigned int) lec, (unsigned int) b_nvram);
}
-
-unlock:
- mutex_unlock(&volume_mutex);
}
static int volume_get_status_ec(u8 *status)
@@ -7167,12 +7217,14 @@ static int __volume_set_mute_ec(const bool mute)
int rc;
u8 s, n;
- if (mutex_lock_killable(&volume_mutex) < 0)
- return -EINTR;
+ ACQUIRE(mutex_kill, guard)(&volume_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = volume_get_status_ec(&s);
if (rc)
- goto unlock;
+ return rc;
n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
s & ~TP_EC_AUDIO_MUTESW_MSK;
@@ -7183,8 +7235,6 @@ static int __volume_set_mute_ec(const bool mute)
rc = 1;
}
-unlock:
- mutex_unlock(&volume_mutex);
return rc;
}
@@ -7215,12 +7265,14 @@ static int __volume_set_volume_ec(const u8 vol)
if (vol > TP_EC_VOLUME_MAX)
return -EINVAL;
- if (mutex_lock_killable(&volume_mutex) < 0)
- return -EINTR;
+ ACQUIRE(mutex_kill, guard)(&volume_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = volume_get_status_ec(&s);
if (rc)
- goto unlock;
+ return rc;
n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
@@ -7230,8 +7282,6 @@ static int __volume_set_volume_ec(const u8 vol)
rc = 1;
}
-unlock:
- mutex_unlock(&volume_mutex);
return rc;
}
@@ -7262,7 +7312,7 @@ static int volume_set_software_mute(bool startup)
/*
* In software mute mode, the standard codec controls take
- * precendence, so we unmute the ThinkPad HW switch at
+ * precedence, so we unmute the ThinkPad HW switch at
* startup. Just on case there are SAUM-capable ThinkPads
* with level controls, set max HW volume as well.
*/
@@ -7596,7 +7646,7 @@ static int __init volume_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
"mute is supported, volume control is %s\n",
- str_supported(!tp_features.mixer_no_level_control));
+ str_supported_unsupported(!tp_features.mixer_no_level_control));
if (software_mute_requested && volume_set_software_mute(true) == 0) {
software_mute_active = true;
@@ -7981,7 +8031,7 @@ TPACPI_HANDLE(fanw, ec, "FANW", /* E531 */
); /* all others */
/*
- * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the
+ * Uninitialized HFSP quirk: ACPI DSDT and EC fail to initialize the
* HFSP register at boot, so it contains 0x07 but the Thinkpad could
* be in auto mode (0x80).
*
@@ -8135,13 +8185,14 @@ static int fan_get_status_safe(u8 *status)
int rc;
u8 s;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = fan_get_status(&s);
/* NS EC doesn't have register with level settings */
if (!rc && !fan_with_ns_addr)
fan_update_desired_level(s);
- mutex_unlock(&fan_mutex);
if (rc)
return rc;
@@ -8334,8 +8385,10 @@ static int fan_set_level_safe(int level)
if (!fan_control_allowed)
return -EPERM;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
if (level == TPACPI_FAN_LAST_LEVEL)
level = fan_control_desired_level;
@@ -8344,7 +8397,6 @@ static int fan_set_level_safe(int level)
if (!rc)
fan_update_desired_level(level);
- mutex_unlock(&fan_mutex);
return rc;
}
@@ -8356,8 +8408,10 @@ static int fan_set_enable(void)
if (!fan_control_allowed)
return -EPERM;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_FANS:
@@ -8413,8 +8467,6 @@ static int fan_set_enable(void)
rc = -ENXIO;
}
- mutex_unlock(&fan_mutex);
-
if (!rc)
vdbg_printk(TPACPI_DBG_FAN,
"fan control: set fan control register to 0x%02x\n",
@@ -8429,8 +8481,10 @@ static int fan_set_disable(void)
if (!fan_control_allowed)
return -EPERM;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = 0;
switch (fan_control_access_mode) {
@@ -8475,7 +8529,6 @@ static int fan_set_disable(void)
vdbg_printk(TPACPI_DBG_FAN,
"fan control: set fan control register to 0\n");
- mutex_unlock(&fan_mutex);
return rc;
}
@@ -8486,8 +8539,10 @@ static int fan_set_speed(int speed)
if (!fan_control_allowed)
return -EPERM;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = 0;
switch (fan_control_access_mode) {
@@ -8521,7 +8576,6 @@ static int fan_set_speed(int speed)
rc = -ENXIO;
}
- mutex_unlock(&fan_mutex);
return rc;
}
@@ -8681,8 +8735,10 @@ static ssize_t fan_pwm1_store(struct device *dev,
/* scale down from 0-255 to 0-7 */
newlevel = (s >> 5) & 0x07;
- if (mutex_lock_killable(&fan_mutex))
- return -ERESTARTSYS;
+ ACQUIRE(mutex_kill, guard)(&fan_mutex);
+ rc = ACQUIRE_ERR(mutex_kill, &guard);
+ if (rc)
+ return rc;
rc = fan_get_status(&status);
if (!rc && (status &
@@ -8696,7 +8752,6 @@ static ssize_t fan_pwm1_store(struct device *dev,
}
}
- mutex_unlock(&fan_mutex);
return (rc) ? rc : count;
}
@@ -8846,6 +8901,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */
TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */
TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */
+ TPACPI_Q_LNV('J', '4', TPACPI_FAN_NS), /* L440 / L540 */
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */
TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */
@@ -8993,8 +9049,8 @@ static int __init fan_init(struct ibm_init_struct *iibm)
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
"fan is %s, modes %d, %d\n",
- str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
- fan_control_access_mode != TPACPI_FAN_WR_NONE),
+ str_supported_unsupported(fan_status_access_mode != TPACPI_FAN_NONE ||
+ fan_control_access_mode != TPACPI_FAN_WR_NONE),
fan_status_access_mode, fan_control_access_mode);
/* fan control master switch */
@@ -10107,9 +10163,8 @@ static void lcdshadow_resume(void)
if (!lcdshadow_dev)
return;
- mutex_lock(&lcdshadow_dev->lock);
+ guard(mutex)(&lcdshadow_dev->lock);
lcdshadow_set_sw_state(lcdshadow_dev, lcdshadow_dev->sw_state);
- mutex_unlock(&lcdshadow_dev->lock);
}
static int lcdshadow_read(struct seq_file *m)
@@ -10141,9 +10196,8 @@ static int lcdshadow_write(char *buf)
if (state >= 2 || state < 0)
return -EINVAL;
- mutex_lock(&lcdshadow_dev->lock);
- res = lcdshadow_set_sw_state(lcdshadow_dev, state);
- mutex_unlock(&lcdshadow_dev->lock);
+ scoped_guard(mutex, &lcdshadow_dev->lock)
+ res = lcdshadow_set_sw_state(lcdshadow_dev, state);
drm_privacy_screen_call_notifier_chain(lcdshadow_dev);
@@ -10547,13 +10601,14 @@ static int dytc_profile_set(struct device *dev,
int output;
int err;
- err = mutex_lock_interruptible(&dytc_mutex);
+ ACQUIRE(mutex_intr, guard)(&dytc_mutex);
+ err = ACQUIRE_ERR(mutex_intr, &guard);
if (err)
return err;
err = convert_profile_to_dytc(profile, &perfmode);
if (err)
- goto unlock;
+ return err;
if (dytc_capabilities & BIT(DYTC_FC_MMC)) {
if (profile == PLATFORM_PROFILE_BALANCED) {
@@ -10561,22 +10616,22 @@ static int dytc_profile_set(struct device *dev,
* To get back to balanced mode we need to issue a reset command.
* Note we still need to disable CQL mode before hand and re-enable
* it afterwards, otherwise dytc_lapmode gets reset to 0 and stays
- * stuck at 0 for aprox. 30 minutes.
+ * stuck at 0 for approx. 30 minutes.
*/
err = dytc_cql_command(DYTC_CMD_RESET, &output);
if (err)
- goto unlock;
+ return err;
} else {
/* Determine if we are in CQL mode. This alters the commands we do */
err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1),
&output);
if (err)
- goto unlock;
+ return err;
}
} else if (dytc_capabilities & BIT(DYTC_FC_PSC)) {
err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output);
if (err)
- goto unlock;
+ return err;
/* system supports AMT, activate it when on balanced */
if (dytc_capabilities & BIT(DYTC_FC_AMT))
@@ -10584,8 +10639,6 @@ static int dytc_profile_set(struct device *dev,
}
/* Success - update current profile */
dytc_current_profile = profile;
-unlock:
- mutex_unlock(&dytc_mutex);
return err;
}
@@ -10607,26 +10660,26 @@ static const struct platform_profile_ops dytc_profile_ops = {
static void dytc_profile_refresh(void)
{
enum platform_profile_option profile;
- int output = 0, err = 0;
+ int output = 0, err;
int perfmode, funcmode = 0;
- mutex_lock(&dytc_mutex);
- if (dytc_capabilities & BIT(DYTC_FC_MMC)) {
- if (dytc_mmc_get_available)
- err = dytc_command(DYTC_CMD_MMC_GET, &output);
- else
- err = dytc_cql_command(DYTC_CMD_GET, &output);
- funcmode = DYTC_FUNCTION_MMC;
- } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) {
- err = dytc_command(DYTC_CMD_GET, &output);
- /* Check if we are PSC mode, or have AMT enabled */
- funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF;
- } else { /* Unknown profile mode */
- err = -ENODEV;
+ scoped_guard(mutex, &dytc_mutex) {
+ if (dytc_capabilities & BIT(DYTC_FC_MMC)) {
+ if (dytc_mmc_get_available)
+ err = dytc_command(DYTC_CMD_MMC_GET, &output);
+ else
+ err = dytc_cql_command(DYTC_CMD_GET, &output);
+ funcmode = DYTC_FUNCTION_MMC;
+ } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) {
+ err = dytc_command(DYTC_CMD_GET, &output);
+ /* Check if we are PSC mode, or have AMT enabled */
+ funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF;
+ } else { /* Unknown profile mode */
+ err = -ENODEV;
+ }
+ if (err)
+ return;
}
- mutex_unlock(&dytc_mutex);
- if (err)
- return;
perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
err = convert_dytc_to_profile(funcmode, perfmode, &profile);
@@ -11015,24 +11068,23 @@ static int auxmac_init(struct ibm_init_struct *iibm)
{
acpi_status status;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
- union acpi_object *obj;
status = acpi_evaluate_object(NULL, "\\MACA", NULL, &buffer);
-
if (ACPI_FAILURE(status))
return -ENODEV;
- obj = buffer.pointer;
-
- if (obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) {
+ union acpi_object *obj __free(kfree) = buffer.pointer;
+ if (!obj || obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) {
pr_info("Invalid buffer for MAC address pass-through.\n");
- goto auxmacinvalid;
+ strscpy(auxmac, "unavailable", sizeof(auxmac));
+ return 0;
}
if (obj->string.pointer[AUXMAC_BEGIN_MARKER] != '#' ||
obj->string.pointer[AUXMAC_END_MARKER] != '#') {
pr_info("Invalid header for MAC address pass-through.\n");
- goto auxmacinvalid;
+ strscpy(auxmac, "unavailable", sizeof(auxmac));
+ return 0;
}
if (strncmp(obj->string.pointer + AUXMAC_START, "XXXXXXXXXXXX", AUXMAC_LEN) != 0)
@@ -11040,13 +11092,7 @@ static int auxmac_init(struct ibm_init_struct *iibm)
else
strscpy(auxmac, "disabled", sizeof(auxmac));
-free:
- kfree(obj);
return 0;
-
-auxmacinvalid:
- strscpy(auxmac, "unavailable", sizeof(auxmac));
- goto free;
}
static struct ibm_struct auxmac_data = {
@@ -11538,7 +11584,7 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
if (tp_features.kbdlight) {
enum led_brightness brightness;
- mutex_lock(&kbdlight_mutex);
+ guard(mutex)(&kbdlight_mutex);
/*
* Check the brightness actually changed, setting the brightness
@@ -11550,8 +11596,6 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
led_classdev_notify_brightness_hw_changed(
&tpacpi_led_kbdlight.led_classdev, brightness);
}
-
- mutex_unlock(&kbdlight_mutex);
}
/* Key events are suppressed by default hotkey_user_mask */
return false;
@@ -11573,11 +11617,11 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
enum drm_privacy_screen_status old_hw_state;
bool changed;
- mutex_lock(&lcdshadow_dev->lock);
- old_hw_state = lcdshadow_dev->hw_state;
- lcdshadow_get_hw_state(lcdshadow_dev);
- changed = lcdshadow_dev->hw_state != old_hw_state;
- mutex_unlock(&lcdshadow_dev->lock);
+ scoped_guard(mutex, &lcdshadow_dev->lock) {
+ old_hw_state = lcdshadow_dev->hw_state;
+ lcdshadow_get_hw_state(lcdshadow_dev);
+ changed = lcdshadow_dev->hw_state != old_hw_state;
+ }
if (changed)
drm_privacy_screen_call_notifier_chain(lcdshadow_dev);
@@ -11598,12 +11642,10 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
pr_err("Error retrieving camera shutter state after shutter event\n");
return true;
}
- mutex_lock(&tpacpi_inputdev_send_mutex);
-
- input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state);
- input_sync(tpacpi_inputdev);
-
- mutex_unlock(&tpacpi_inputdev_send_mutex);
+ scoped_guard(mutex, &tpacpi_inputdev_send_mutex) {
+ input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state);
+ input_sync(tpacpi_inputdev);
+ }
return true;
case TP_HKEY_EV_DOUBLETAP_TOGGLE:
/* Toggle kernel-level doubletap event filtering */
@@ -11627,20 +11669,11 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
static struct proc_dir_entry *proc_dir;
/*
- * Module and infrastructure proble, init and exit handling
+ * Module and infrastructure probe, init and exit handling
*/
static bool force_load;
-#ifdef CONFIG_THINKPAD_ACPI_DEBUG
-static const char * __init str_supported(int is_supported)
-{
- static char text_unsupported[] __initdata = "not supported";
-
- return (is_supported) ? &text_unsupported[4] : &text_unsupported[0];
-}
-#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
-
static struct dentry *tpacpi_dbg;
static void tpacpi_debugfs_init(void)
{
@@ -11925,7 +11958,7 @@ static int __must_check __init get_thinkpad_model_data(
static int __init probe_for_thinkpad(void)
{
- int is_thinkpad;
+ bool is_thinkpad;
if (acpi_disabled)
return -ENODEV;
@@ -12360,6 +12393,8 @@ static int __init thinkpad_acpi_module_init(void)
dmi_id = dmi_first_match(fwbug_list);
if (dmi_id)
tp_features.quirks = dmi_id->driver_data;
+ tp_features.has_keyboard_attached_on_screen =
+ dmi_check_system(keyboard_attached_on_screen_list);
/* Device initialization */
tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, PLATFORM_DEVID_NONE,
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index d5e961566136..1d44ff83e157 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -29,6 +29,7 @@
#include <linux/acpi.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/debugfs.h>
@@ -38,6 +39,7 @@
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/limits.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mutex_types.h>
@@ -45,9 +47,13 @@
#include <linux/overflow.h>
#include <linux/seq_file.h>
#include <linux/stddef.h>
+#include <linux/string.h>
#include <linux/types.h>
+#include <linux/uuid.h>
#include <linux/wmi.h>
+#include <asm/barrier.h>
+
#include "wmi-capdata.h"
#include "wmi-helpers.h"
@@ -70,14 +76,19 @@ enum lwmi_cd_type {
CD_TYPE_NONE = -1,
};
-#define LWMI_CD_TABLE_ITEM(_type) \
- [_type] = { \
- .name = #_type, \
- .type = _type, \
+static const struct wmi_device_id lwmi_cd_id_table[];
+
+/* Save some .rodata space by reusing the GUID strings from the id table. */
+#define LWMI_CD_TABLE_ITEM(_type) \
+ [_type] = { \
+ .name = #_type, \
+ .guid_string = lwmi_cd_id_table[_type].guid_string, \
+ .type = _type, \
}
static const struct lwmi_cd_info {
const char *name;
+ const char *guid_string;
enum lwmi_cd_type type;
} lwmi_cd_table[] = {
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
@@ -87,9 +98,11 @@ static const struct lwmi_cd_info {
struct lwmi_cd_priv {
struct notifier_block acpi_nb; /* ACPI events */
+ const struct lwmi_cd_info *info;
struct wmi_device *wdev;
struct cd_list *list;
struct dentry *debugfs_dir;
+ bool initialized;
/*
* A capdata device may be a component master of another capdata device.
@@ -101,14 +114,13 @@ struct lwmi_cd_priv {
struct lwmi_cd_sub_master_priv {
struct device *master_dev;
cd_list_cb_t master_cb;
- struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV): stubbed sub-component. */
bool registered; /* Has the sub-master been registered? */
- } *sub_master;
+ } *sub_master; /* NULL: no sub-component. */
};
struct cd_list {
struct mutex list_mutex; /* list R/W mutex */
- enum lwmi_cd_type type;
u8 count;
union {
@@ -137,7 +149,7 @@ static int lwmi_cd_match(struct device *dev, void *type)
return false;
priv = dev_get_drvdata(dev);
- return priv->list->type == *(enum lwmi_cd_type *)type;
+ return priv->info->type == *(enum lwmi_cd_type *)type;
}
/**
@@ -147,24 +159,49 @@ static int lwmi_cd_match(struct device *dev, void *type)
*
* Adds all component matches to the list stored in @matchptr for the @master
* device. @matchptr must be initialized to NULL.
+ *
+ * Missing components are not added to the match list, or else the master waits
+ * for them forever. For some components, their existence can be determined by
+ * querying other components, so they are handled as sub-components and thus not
+ * matched here. For components with no existence declaration, wmidev_exists()
+ * is used to determine their existence.
+ *
+ * If all components are missing, ERR_PTR(-ENXIO) is returned through @matchptr.
*/
void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
{
- int i;
+ bool matched = false;
+ int ret, i;
if (WARN_ON(*matchptr))
return;
for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ guid_t guid;
+
/* Skip sub-components. */
if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
continue;
+ ret = guid_parse(lwmi_cd_table[i].guid_string, &guid);
+ if (ret < 0) {
+ *matchptr = ERR_PTR(ret);
+ return;
+ }
+
+ if (!wmidev_exists(&guid))
+ continue;
+
component_match_add(master, matchptr, lwmi_cd_match,
(void *)&lwmi_cd_table[i].type);
if (IS_ERR(*matchptr))
return;
+
+ matched = true;
}
+
+ if (!matched)
+ *matchptr = ERR_PTR(-ENXIO);
}
EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
@@ -254,10 +291,16 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
struct lwmi_cd_binder *binder = data;
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
binder->cd00_list = priv->list;
+ /* Missing sub-master info implies missing sub-component. */
+ if (!priv->sub_master) {
+ binder->cd_fan_list_cb(om_dev, NULL);
+ break;
+ }
+
priv->sub_master->master_dev = om_dev;
priv->sub_master->master_cb = binder->cd_fan_list_cb;
lwmi_cd_call_master_cb(priv);
@@ -288,13 +331,9 @@ static void lwmi_cd_component_unbind(struct device *cd_dev,
{
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
- switch (priv->list->type) {
- case LENOVO_CAPABILITY_DATA_00:
+ if (priv->sub_master) {
priv->sub_master->master_dev = NULL;
priv->sub_master->master_cb = NULL;
- return;
- default:
- return;
}
}
@@ -308,8 +347,8 @@ static const struct component_ops lwmi_cd_component_ops = {
* @dev: The sub-master capdata basic device.
*
* Call component_bind_all to bind the sub-component device to the sub-master
- * device. On success, collect the pointer to the sub-component list and try
- * to call the master callback.
+ * device. On success, collect the pointer (or ERR_PTR(-ENODEV) if it's stubbed)
+ * to the sub-component list and try to call the master callback.
*
* Return: 0 on success, or an error code.
*/
@@ -323,7 +362,7 @@ static int lwmi_cd_sub_master_bind(struct device *dev)
if (ret)
return ret;
- priv->sub_master->sub_component_list = sub_component_list;
+ priv->sub_master->sub_component_list = sub_component_list ?: ERR_PTR(-ENODEV);
lwmi_cd_call_master_cb(priv);
return 0;
@@ -369,17 +408,19 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
struct component_match *master_match = NULL;
int ret;
+ /*
+ * If the sub-master has no sub-component, there is no need to allocate
+ * or register the sub-master. The master callback will be called with
+ * NULL on master <-> sub-master bind to inform the master about the
+ * absence of sub-component.
+ */
+ if (sub_component_type == CD_TYPE_NONE)
+ return 0;
+
priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
if (!priv->sub_master)
return -ENOMEM;
- if (sub_component_type == CD_TYPE_NONE) {
- /* The master callback will be called with NULL on bind. */
- priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
- priv->sub_master->registered = false;
- return 0;
- }
-
/*
* lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
* data cannot be used here. Steal one from lwmi_cd_table.
@@ -404,7 +445,7 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
*/
static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
{
- if (!priv->sub_master->registered)
+ if (!priv->sub_master || !priv->sub_master->registered)
return;
component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
@@ -440,7 +481,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
/*
* lwmi_cd*_get_data - Get the data of the specified attribute
* @list: The lenovo-wmi-capdata pointer to its cd_list struct.
- * @attribute_id: The capdata attribute ID to be found.
+ * @attribute_id: The capdata attribute ID (non-zero) to be found.
* @output: Pointer to a capdata* struct to return the data.
*
* Retrieves the capability data struct pointer for the given
@@ -448,12 +489,12 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
*
* Return: 0 on success, or -EINVAL.
*/
-#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _output_t) \
int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
{ \
u8 idx; \
\
- if (WARN_ON(list->type != _cd_type)) \
+ if (WARN_ON(!list || !attribute_id)) \
return -EINVAL; \
\
guard(mutex)(&list->list_mutex); \
@@ -466,13 +507,13 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
return -EINVAL; \
}
-DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
+DEF_LWMI_CDXX_GET_DATA(cd00, struct capdata00);
EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
-DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
+DEF_LWMI_CDXX_GET_DATA(cd01, struct capdata01);
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
-DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
+DEF_LWMI_CDXX_GET_DATA(cd_fan, struct capdata_fan);
EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
/* ======== debugfs ======== */
@@ -546,15 +587,14 @@ static int lwmi_cd_debugfs_show(struct seq_file *s, void *data)
guard(mutex)(&priv->list->list_mutex);
- /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */
for (idx = 0; idx < priv->list->count; idx++) {
- seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx);
+ seq_printf(s, "%s[%u]:\n", priv->info->name, idx);
- if (priv->list->type == LENOVO_CAPABILITY_DATA_00)
+ if (priv->info->type == LENOVO_CAPABILITY_DATA_00)
lwmi_cd00_show(s, &priv->list->cd00[idx]);
- else if (priv->list->type == LENOVO_CAPABILITY_DATA_01)
+ else if (priv->info->type == LENOVO_CAPABILITY_DATA_01)
lwmi_cd01_show(s, &priv->list->cd01[idx]);
- else if (priv->list->type == LENOVO_FAN_TEST_DATA)
+ else if (priv->info->type == LENOVO_FAN_TEST_DATA)
lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]);
}
@@ -568,6 +608,9 @@ DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs);
*/
static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
{
+ if (!priv->list)
+ return;
+
priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev);
debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops);
@@ -579,6 +622,7 @@ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
*/
static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
{
+ /* Debugfs can handle NULL dir, no need to check. */
debugfs_remove_recursive(priv->debugfs_dir);
priv->debugfs_dir = NULL;
}
@@ -586,20 +630,104 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
/* ======== WMI interface ======== */
/**
- * lwmi_cd_cache() - Cache all WMI data block information
+ * lwmi_cd_stub() - Stub the device by not providing any capability data
* @priv: lenovo-wmi-capdata driver data.
+ * @err: The occurred error.
*
- * Loop through each WMI data block and cache the data.
+ * The Other Mode driver binds to both Capability Data 00 and 01. If either 00
+ * or 01 fails to probe, the Other Mode device will fail to provide fw-attr or
+ * hwmon/power_supply_ext devices for the other functional capdata device.
+ *
+ * Therefore, errors must be non-fatal in order not to break the assumptions
+ * made by the device component framework, so that the Other Mode device can
+ * provide whatever is functional.
+ *
+ * After stubbing the device, a NULL list will be passed to the Other Mode
+ * device on bind.
+ *
+ * Return: 0 if the @err is suppressed, otherwise its propagated as is.
+ */
+static int lwmi_cd_stub(struct lwmi_cd_priv *priv, int err)
+{
+ /*
+ * No barrier is required in any possible code paths:
+ * - called from lwmi_cd_cache(): a proper barrier has been employed
+ * - called from lwmi_cd_fan_list_alloc_cache(): early setup does not
+ * require synchronization, and priv->initialized must be false
+ */
+ bool initialized = READ_ONCE(priv->initialized);
+
+ dev_warn(&priv->wdev->dev, "%s %s (%u items) due to error: %d\n",
+ initialized ? "clearing" : "stubbing", priv->info->name,
+ priv->list ? priv->list->count : 0, err);
+
+ /* Simply print the warning message. */
+ if (!priv->list)
+ return initialized ? err : 0;
+
+ /*
+ * Stub the device on initialization errors.
+ *
+ * Note that if a transient error occurs during initialization, there is
+ * nothing we can do to re-enable the functionalities of the Other Mode
+ * driver even if a successful runtime notifier call making the relevant
+ * capdata emerge, as registering functionalities without the protection
+ * of the component framework is unsafe. Therefore, registering the
+ * notifier for a stubbed device is meaningless, and we don't need to
+ * distinguish transient and pernament errors during initialization.
+ *
+ * For runtime errors (see below), the registered functionalities
+ * (fw-attr) become temporarily unavailable due to the unavailablilty of
+ * the relevant capdata, and will recover after a successful runtime
+ * notifier call.
+ */
+ if (!initialized) {
+ devm_kfree(&priv->wdev->dev, priv->list);
+ priv->list = NULL;
+
+ return 0;
+ }
+
+ /*
+ * Runtime errors are transient. Simply clear all cached data and
+ * propagate the error.
+ *
+ * Since a valid attribute id is never 0 (the firmware also untilize the
+ * fact to stub some capabilities according to platform metadata),
+ * clearing the cached data effectively makes all attributes temporarily
+ * unavailable until the next notifier call.
+ */
+
+ lockdep_assert_held(&priv->list->list_mutex);
+
+ switch (priv->info->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ memset(priv->list->cd01, 0,
+ flex_array_size(priv->list, cd01, priv->list->count));
+ break;
+ default:
+ unreachable();
+ }
+
+ return err;
+}
+
+/**
+ * __lwmi_cd_cache() - Cache all WMI data block information locklessly
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Loop through each WMI data block and cache the data locklessly.
*
* Return: 0 on success, or an error.
*/
-static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
{
+ bool got_data = false;
size_t size;
int idx;
void *p;
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
p = &priv->list->cd00[0];
size = sizeof(priv->list->cd00[0]);
@@ -615,109 +743,165 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
return -EINVAL;
}
- guard(mutex)(&priv->list->list_mutex);
for (idx = 0; idx < priv->list->count; idx++, p += size) {
- union acpi_object *ret_obj __free(kfree) = NULL;
+ struct wmi_buffer wbuf;
+ int ret;
- ret_obj = wmidev_block_query(priv->wdev, idx);
- if (!ret_obj)
- return -ENODEV;
-
- if (ret_obj->type != ACPI_TYPE_BUFFER ||
- ret_obj->buffer.length < size)
+ ret = wmidev_query_block(priv->wdev, idx, &wbuf, size);
+ if (ret == -ENODATA) /* The block is too short, probably stubbed. */
continue;
+ if (ret)
+ return lwmi_cd_stub(priv, ret);
- memcpy(p, ret_obj->buffer.pointer, size);
+ /* Capdata 01 is an extension to capdata 00. */
+ struct capdata00 *capdata __free(kfree) = wbuf.data;
+
+ memcpy(p, capdata, size);
+
+ /*
+ * A valid attribute always has a non-zero id. IOW, a zero id
+ * implies a stubbed item (or the query method being stubbed).
+ *
+ * The stubbed item is still copied to the cache slot anyway, in
+ * case the firmware dynamically stubs the capdata item after
+ * switching Game Zone mode. In this manner, the previously
+ * exposed capdata can be void.
+ */
+ if (capdata->id)
+ got_data = true;
}
+ /*
+ * Some devices may stub the query method because it doesn't support the
+ * relevant interfaces at all. For example, most ThinkBook devices
+ * doesn't support Game Zone or Other Mode thermal tuning, so capdata 01
+ * is useless and stubbed.
+ *
+ * Stub the device and release needless resources.
+ */
+ if (!got_data)
+ return lwmi_cd_stub(priv, -ENODATA);
+
return 0;
}
/**
+ * lwmi_cd_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+{
+ if (!priv->list)
+ return 0;
+
+ /* Synchronize mutex initialization. Paired with lwmi_cd_finalize(). */
+ if (!smp_load_acquire(&priv->initialized))
+ return __lwmi_cd_cache(priv);
+
+ switch (priv->info->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->list->list_mutex);
+ return __lwmi_cd_cache(priv);
+}
+
+struct lwmi_cd_fan_block {
+ u32 nr;
+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
+} __packed;
+
+/**
* lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
* @priv: lenovo-wmi-capdata driver data.
- * @listptr: Pointer to returned cd_list pointer.
*
- * Return: count of fans found, or an error.
+ * Return: 0 on success, or an error.
*/
-static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
{
+ struct wmi_buffer wbuf;
+ bool got_data = false;
struct cd_list *list;
- size_t size;
+ int ret, idx;
u32 count;
- int idx;
- /* Emit unaligned access to u8 buffer with __packed. */
- struct cd_fan_block {
- u32 nr;
- u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
- } __packed * block;
+ ret = wmidev_query_block(priv->wdev, 0, &wbuf, sizeof(struct lwmi_cd_fan_block));
+ if (ret == -ENODATA) /* The block is too short, probably stubbed. */
+ return 0;
+ if (ret)
+ return lwmi_cd_stub(priv, ret); /* Print the warning message. */
- union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
- if (!ret_obj)
- return -ENODEV;
+ struct lwmi_cd_fan_block *block __free(kfree) = wbuf.data;
- if (ret_obj->type == ACPI_TYPE_BUFFER) {
- block = (struct cd_fan_block *)ret_obj->buffer.pointer;
- size = ret_obj->buffer.length;
+ count = block->nr;
- count = size >= sizeof(*block) ? block->nr : 0;
- if (size < struct_size(block, data, count * 3)) {
- dev_warn(&priv->wdev->dev,
- "incomplete fan test data block: %zu < %zu, ignoring\n",
- size, struct_size(block, data, count * 3));
- count = 0;
- } else if (count > U8_MAX) {
- dev_warn(&priv->wdev->dev,
- "too many fans reported: %u > %u, truncating\n",
- count, U8_MAX);
- count = U8_MAX;
- }
- } else {
- /*
- * This is usually caused by a dummy ACPI method. Do not return an error
- * as failing to probe this device will result in sub-master device being
- * unbound. This behavior aligns with lwmi_cd_cache().
- */
+ if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, ignoring\n", count, U8_MAX);
+ count = 0;
+ } else if (wbuf.length < struct_size(block, data, count * 3)) {
+ dev_warn(&priv->wdev->dev,
+ "incomplete fan test data block: %zu < %zu (%u fans), ignoring\n",
+ wbuf.length, struct_size(block, data, count * 3), count);
count = 0;
}
+ if (!count)
+ return 0;
+
list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
if (!list)
return -ENOMEM;
+ list->count = count;
+ priv->list = list;
+
for (idx = 0; idx < count; idx++) {
- /* Do not calculate array index using count, as it may be truncated. */
+ if (!block->data[idx]) /* Ignore stub. */
+ continue;
+
list->cd_fan[idx] = (struct capdata_fan) {
.id = block->data[idx],
- .max_rpm = block->data[idx + block->nr],
- .min_rpm = block->data[idx + (2 * block->nr)],
+ .max_rpm = block->data[idx + count],
+ .min_rpm = block->data[idx + (2 * count)],
};
+
+ got_data = true;
}
- *listptr = list;
- return count;
+ if (!got_data)
+ return lwmi_cd_stub(priv, -ENODATA);
+
+ return 0;
}
/**
* lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
* @priv: lenovo-wmi-capdata driver data.
- * @type: The type of capability data.
*
* Allocate a cd_list struct large enough to contain data from all WMI data
* blocks provided by the interface.
*
* Return: 0 on success, or an error.
*/
-static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
{
struct cd_list *list;
size_t list_size;
- int count, ret;
+ int count;
count = wmidev_instance_count(priv->wdev);
+ if (!count)
+ return 0;
- switch (type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
list_size = struct_size(list, cd00, count);
break;
@@ -725,11 +909,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
list_size = struct_size(list, cd01, count);
break;
case LENOVO_FAN_TEST_DATA:
- count = lwmi_cd_fan_list_alloc_cache(priv, &list);
- if (count < 0)
- return count;
-
- goto got_list;
+ return lwmi_cd_fan_list_alloc_cache(priv);
default:
return -EINVAL;
}
@@ -738,12 +918,6 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
if (!list)
return -ENOMEM;
-got_list:
- ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
- if (ret)
- return ret;
-
- list->type = type;
list->count = count;
priv->list = list;
@@ -751,9 +925,29 @@ got_list:
}
/**
+ * lwmi_cd_finalize() - Finalize the capability data initialization
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_finalize(struct lwmi_cd_priv *priv)
+{
+ int ret;
+
+ if (priv->list) {
+ ret = devm_mutex_init(&priv->wdev->dev, &priv->list->list_mutex);
+ if (ret)
+ return ret;
+ }
+
+ /* Synchronize mutex initialization. Paired with lwmi_cd_cache(). */
+ smp_store_release(&priv->initialized, 1);
+ return 0;
+}
+
+/**
* lwmi_cd_setup() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
- * @type: The type of capability data.
*
* Allocate a cd_list struct large enough to contain data from all WMI data
* blocks provided by the interface. Then loop through each data block and
@@ -761,26 +955,28 @@ got_list:
*
* Return: 0 on success, or an error code.
*/
-static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+static int lwmi_cd_setup(struct lwmi_cd_priv *priv)
{
int ret;
- ret = lwmi_cd_alloc(priv, type);
+ ret = lwmi_cd_alloc(priv);
+ if (ret)
+ return ret;
+
+ ret = lwmi_cd_cache(priv);
if (ret)
return ret;
- return lwmi_cd_cache(priv);
+ return lwmi_cd_finalize(priv);
}
/**
* lwmi_cd01_notifier_call() - Call method for cd01 notifier.
- * block call chain.
- * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @nb: The cd01 ACPI notifier_block.
* @action: Unused.
* @data: The ACPI event.
*
- * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
- * of a change.
+ * Refresh cached capdata on AC status change.
*
* Return: notifier_block status.
*/
@@ -799,8 +995,16 @@ static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long acti
switch (event->type) {
case ACPI_AC_NOTIFY_STATUS:
ret = lwmi_cd_cache(priv);
- if (ret)
- return NOTIFY_BAD;
+ if (ret) {
+ dev_warn(&priv->wdev->dev,
+ "failed to refresh capdata on AC change: %d\n", ret);
+
+ /*
+ * Our error is non-fatal for others, and we still want
+ * the other event consumers to process it.
+ */
+ return NOTIFY_DONE;
+ }
return NOTIFY_OK;
default:
@@ -832,10 +1036,11 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
if (!priv)
return -ENOMEM;
+ priv->info = info;
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- ret = lwmi_cd_setup(priv, info->type);
+ ret = lwmi_cd_setup(priv);
if (ret)
goto out;
@@ -844,7 +1049,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
struct capdata00 capdata00;
- ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
+ ret = priv->list ? lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST,
+ &capdata00) : -ENODATA;
if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
sub_component_type = CD_TYPE_NONE;
@@ -865,14 +1071,16 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
case LENOVO_CAPABILITY_DATA_01:
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
- ret = register_acpi_notifier(&priv->acpi_nb);
- if (ret)
- goto out;
+ if (priv->list) {
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ goto out;
- ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
- &priv->acpi_nb);
- if (ret)
- goto out;
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
+ }
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
goto out;
@@ -890,7 +1098,7 @@ out:
lwmi_cd_debugfs_add(priv);
dev_dbg(&wdev->dev, "registered %s with %u items\n",
- info->name, priv->list->count);
+ info->name, priv->list ? priv->list->count : 0);
}
return ret;
}
@@ -901,7 +1109,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
lwmi_cd_debugfs_remove(priv);
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
lwmi_cd_sub_master_del(priv);
fallthrough;
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index 8f5766c391eb..687caa226263 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -24,9 +24,10 @@
#include <linux/export.h>
#include <linux/module.h>
#include <linux/notifier.h>
-#include <linux/unaligned.h>
#include <linux/wmi.h>
+#include <asm/byteorder.h>
+
#include "wmi-helpers.h"
/* Thermal mode notifier chain. */
@@ -42,49 +43,33 @@ static BLOCKING_NOTIFIER_HEAD(tm_chain_head);
* @size: Length of the buffer.
* @retval: Pointer for the return value to be assigned.
*
- * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
- * integer. Validates the return value type and assigns the value to the
- * retval pointer.
+ * Calls the specified WMI method for Lenovo WMI devices that return a WMI
+ * integer. Validates the return value and assigns it to @retval when it's not
+ * NULL, otherwise the return value is not validated.
*
* Return: 0 on success, or an error code.
*/
int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
unsigned char *buf, size_t size, u32 *retval)
{
- struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
- struct acpi_buffer input = { size, buf };
- acpi_status status;
-
- status = wmidev_evaluate_method(wdev, instance, method_id, &input,
- &output);
- if (ACPI_FAILURE(status))
- return -EIO;
-
- union acpi_object *ret_obj __free(kfree) = output.pointer;
-
- if (retval) {
- if (!ret_obj)
- return -ENODATA;
-
- switch (ret_obj->type) {
- /*
- * The ACPI method may simply return a buffer when a u32
- * is expected. This is valid on Windows as its WMI-ACPI
- * driver converts everything to a common buffer.
- */
- case ACPI_TYPE_BUFFER:
- if (ret_obj->buffer.length < sizeof(u32))
- return -ENXIO;
-
- *retval = get_unaligned_le32(ret_obj->buffer.pointer);
- return 0;
- case ACPI_TYPE_INTEGER:
- *retval = (u32)ret_obj->integer.value;
- return 0;
- default:
- return -ENXIO;
- }
- }
+ struct wmi_buffer input = {
+ .length = size,
+ .data = buf,
+ };
+ struct wmi_buffer output;
+ int ret;
+
+ if (!retval)
+ return wmidev_invoke_procedure(wdev, instance, method_id, &input);
+
+ ret = wmidev_invoke_method(wdev, instance, method_id, &input,
+ &output, sizeof(__le32));
+ if (ret)
+ return ret;
+
+ __le32 *valp __free(kfree) = output.data;
+
+ *retval = le32_to_cpu(*valp);
return 0;
};
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index b175971bb308..cecb89bb08eb 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -1619,11 +1619,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
* lenovo-wmi-other master driver, with a callback to collect fan info from
* capdata_fan. On success, assign the capability data list pointers to the
* driver data struct for later access. These pointers are only valid while the
- * capdata interfaces exist. Finally, collect fan info from capdata00 and
- * register all firmware attribute groups. Note that the HWMON device is
- * registered only if all fan info is collected. Hence, it is not registered
- * here. See lwmi_om_fan_info_collect_cd00() and
- * lwmi_om_fan_info_collect_cd_fan().
+ * capdata interfaces exist.
+ *
+ * If capdata00 is bound, collect fan info from capdata00 and register the power
+ * supply extension. Note that the HWMON device is registered only if all fan
+ * info is collected. Hence, it is not registered here. See
+ * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan().
+ *
+ * If capdata01 is bound, register all firmware attribute groups.
*
* Return: 0 on success, or an error code.
*/
@@ -1643,16 +1646,19 @@ static int lwmi_om_master_bind(struct device *dev)
priv->cd00_list = binder.cd00_list;
priv->cd01_list = binder.cd01_list;
- if (!priv->cd00_list || !priv->cd01_list) {
+ if (!priv->cd00_list && !priv->cd01_list) {
component_unbind_all(dev, NULL);
return -ENODEV;
}
- lwmi_om_fan_info_collect_cd00(priv);
- lwmi_om_psy_ext_init(priv);
+ if (priv->cd00_list) {
+ lwmi_om_fan_info_collect_cd00(priv);
+ lwmi_om_psy_ext_init(priv);
+ }
- lwmi_om_fw_attr_add(priv);
+ if (priv->cd01_list)
+ lwmi_om_fw_attr_add(priv);
return 0;
}
diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c
index 8a91d440724b..f35c307300c2 100644
--- a/drivers/platform/x86/lenovo/yogabook.c
+++ b/drivers/platform/x86/lenovo/yogabook.c
@@ -120,10 +120,7 @@ static void yogabook_toggle_digitizer_mode(struct yogabook_data *data)
if (test_bit(YB_SUSPENDED, &data->flags))
return;
- if (test_bit(YB_DIGITIZER_MODE, &data->flags))
- clear_bit(YB_DIGITIZER_MODE, &data->flags);
- else
- set_bit(YB_DIGITIZER_MODE, &data->flags);
+ change_bit(YB_DIGITIZER_MODE, &data->flags);
/*
* We are called from the ACPI core and the driver [un]binding which is
@@ -136,10 +133,8 @@ static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data)
{
struct yogabook_data *data = _data;
- if (gpiod_get_value(data->backside_hall_gpio))
- set_bit(YB_TABLET_MODE, &data->flags);
- else
- clear_bit(YB_TABLET_MODE, &data->flags);
+ assign_bit(YB_TABLET_MODE, &data->flags,
+ gpiod_get_value(data->backside_hall_gpio));
schedule_work(&data->work);
diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c
index 566dfc73cb67..5247c25fcdf6 100644
--- a/drivers/platform/x86/msi-ec.c
+++ b/drivers/platform/x86/msi-ec.c
@@ -122,6 +122,7 @@ static const char * const ALLOWED_FW_1[] __initconst = {
"17F2EMS1.104",
"17F2EMS1.106",
"17F2EMS1.107",
+ "16U8EMS2.100", // MSI GL65 Leopard 9SCXK
NULL
};
@@ -825,6 +826,7 @@ static struct msi_ec_conf CONF9 __initdata = {
static const char * const ALLOWED_FW_10[] __initconst = {
"1582EMS1.107", // GF66 11UC
"1583EMS1.109", // Pulse GL66 12UEK
+ "1585EMS1.115", // Katana 15 B13VFK
NULL
};
@@ -1132,6 +1134,7 @@ static struct msi_ec_conf CONF13 __initdata = {
};
static const char * const ALLOWED_FW_14[] __initconst = {
+ "1571EMS1.106", // Creator Z16 A11UE
"1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824)
"182LIMS1.108", // Vector A18 HX A9WHG
"182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG
@@ -1279,6 +1282,15 @@ static ssize_t charge_control_threshold_show(u8 offset,
if (result < 0)
return result;
+ /*
+ * The EC may hold an out-of-range value (e.g. 0x80) when no charge
+ * limit has been set. Report the maximum threshold instead of a
+ * meaningless (possibly negative) percentage.
+ */
+ if (rdata < conf.charge_control.range_min ||
+ rdata > conf.charge_control.range_max)
+ rdata = conf.charge_control.range_max;
+
return sysfs_emit(buf, "%i\n", rdata - offset);
}
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
index ddef6b78d2fa..0a0c756a5598 100644
--- a/drivers/platform/x86/msi-laptop.c
+++ b/drivers/platform/x86/msi-laptop.c
@@ -117,7 +117,7 @@ struct quirk_entry {
* Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to
* disable the original Wlan/Bluetooth control by BIOS when user press
* fn key, then control Wlan/Bluetooth/3G by SCM (software control by
- * OS). Without SCM, user cann't on/off 3G module on those 3G netbook.
+ * OS). Without SCM, user can't on/off 3G module on those 3G netbook.
* On Linux, msi-laptop driver will do the same thing to disable the
* original BIOS control, then might need use HAL or other userland
* application to do the software control that simulate with SCM.
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
index c9db750fe5ae..dc56e8da0cdb 100644
--- a/drivers/platform/x86/msi-wmi.c
+++ b/drivers/platform/x86/msi-wmi.c
@@ -33,25 +33,27 @@ MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID);
MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID);
enum msi_scancodes {
- /* Generic MSI keys (not present on MSI Wind) */
- MSI_KEY_BRIGHTNESSUP = 0xD0,
- MSI_KEY_BRIGHTNESSDOWN,
- MSI_KEY_VOLUMEUP,
- MSI_KEY_VOLUMEDOWN,
- MSI_KEY_MUTE,
- /* MSI Wind keys */
WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */
- WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */
- WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */
- WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */
- WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */
- WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */
- /* MSI Claw keys */
+ WIND_KEY_FN_NB = 0x0b,
+ WIND_KEY_MICMUTE_NB = 0x19,
+ WIND_KEY_SETTINGS_NB = 0x1d,
CLAW_KEY_VOLUMEDOWN = 0x21,
+ WIND_KEY_MUTE_NB = 0x23,
CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */
CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */
CLAW_KEY_VOLUMEUP = 0x32,
+ WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */
+ WIND_KEY_CAMERA = 0x57, /* Fn+F6 webcam toggle */
CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */
+ WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */
+ WIND_KEY_TURBO = 0x60, /* Fn+F10 turbo mode toggle */
+ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */
+ WIND_KEY_BLUETOOTH_NB = 0x78,
+ MSI_KEY_BRIGHTNESSUP = 0xd0,
+ MSI_KEY_BRIGHTNESSDOWN = 0xd1,
+ MSI_KEY_VOLUMEUP = 0xd2,
+ MSI_KEY_VOLUMEDOWN = 0xd3,
+ MSI_KEY_MUTE = 0xd4,
};
static struct key_entry msi_wmi_keymap[] = {
{ KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} },
@@ -66,6 +68,13 @@ static struct key_entry msi_wmi_keymap[] = {
{ KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} },
{ KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} },
+ /* These MSI Notebook keys work without WMI. Ignore them to avoid double keycodes */
+ { KE_IGNORE, WIND_KEY_FN_NB },
+ { KE_IGNORE, WIND_KEY_MICMUTE_NB },
+ { KE_IGNORE, WIND_KEY_SETTINGS_NB },
+ { KE_IGNORE, WIND_KEY_MUTE_NB },
+ { KE_IGNORE, WIND_KEY_BLUETOOTH_NB },
+
/* These are unknown WMI events found on MSI Wind */
{ KE_IGNORE, 0x00 },
{ KE_IGNORE, 0x62 },
@@ -236,9 +245,9 @@ static void msi_wmi_notify(union acpi_object *obj, void *context)
}
/* Brightness is served via acpi video driver */
- if (key->type == KE_KEY &&
- (backlight || (key->code == MSI_KEY_BRIGHTNESSUP ||
- key->code == MSI_KEY_BRIGHTNESSDOWN)))
+ if (key->type == KE_KEY && !backlight &&
+ (key->code == MSI_KEY_BRIGHTNESSUP ||
+ key->code == MSI_KEY_BRIGHTNESSDOWN))
return;
pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode);
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
index 34bb17fca148..c4c4babe8e30 100644
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -50,6 +50,7 @@ enum oxp_board {
oxp_x1,
oxp_g1_i,
oxp_g1_a,
+ oxp_apex,
};
static enum oxp_board board;
@@ -93,11 +94,14 @@ static struct device *oxp_dev;
#define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */
#define OXP_X1_CHARGE_INHIBIT_REG 0xA4 /* X1 bypass charging */
-#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01
+#define OXP_APEX_CHARGE_LIMIT_REG 0xE5 /* Apex charge limit (%) */
+#define OXP_APEX_CHARGE_INHIBIT_REG 0xE6 /* Apex bypass charging */
+
+#define OXP_CHARGE_INHIBIT_MASK_AWAKE 0x01
/* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */
-#define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02
-#define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \
- OXP_X1_CHARGE_INHIBIT_MASK_OFF)
+#define OXP_CHARGE_INHIBIT_MASK_OFF 0x02
+#define OXP_CHARGE_INHIBIT_MASK_ALWAYS (OXP_CHARGE_INHIBIT_MASK_AWAKE | \
+ OXP_CHARGE_INHIBIT_MASK_OFF)
static const struct dmi_system_id dmi_table[] = {
{
@@ -154,7 +158,7 @@ static const struct dmi_system_id dmi_table[] = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER APEX"),
},
- .driver_data = (void *)oxp_fly,
+ .driver_data = (void *)oxp_apex,
},
{
.matches = {
@@ -222,6 +226,13 @@ static const struct dmi_system_id dmi_table[] = {
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 3"),
+ },
+ .driver_data = (void *)oxp_g1_i,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
},
.driver_data = (void *)oxp_mini_amd_a07,
@@ -352,6 +363,7 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj,
case oxp_x1:
case oxp_g1_i:
case oxp_g1_a:
+ case oxp_apex:
return attr->mode;
default:
break;
@@ -381,6 +393,7 @@ static ssize_t tt_toggle_store(struct device *dev,
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
+ case oxp_apex:
reg = OXP_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
@@ -427,6 +440,7 @@ static ssize_t tt_toggle_show(struct device *dev,
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
+ case oxp_apex:
reg = OXP_TURBO_SWITCH_REG;
mask = OXP_TURBO_TAKE_VAL;
break;
@@ -524,6 +538,7 @@ static bool oxp_psy_ext_supported(void)
case oxp_g1_i:
case oxp_g1_a:
case oxp_fly:
+ case oxp_apex:
return true;
default:
break;
@@ -537,12 +552,24 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
+ u8 limit_reg, inhibit_reg;
long raw_val;
int ret;
+ switch (board) {
+ case oxp_apex:
+ limit_reg = OXP_APEX_CHARGE_LIMIT_REG;
+ inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG;
+ break;
+ default:
+ limit_reg = OXP_X1_CHARGE_LIMIT_REG;
+ inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG;
+ break;
+ }
+
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
- ret = read_from_ec(OXP_X1_CHARGE_LIMIT_REG, 1, &raw_val);
+ ret = read_from_ec(limit_reg, 1, &raw_val);
if (ret)
return ret;
if (raw_val < 0 || raw_val > 100)
@@ -550,14 +577,14 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy,
val->intval = raw_val;
return 0;
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
- ret = read_from_ec(OXP_X1_CHARGE_INHIBIT_REG, 1, &raw_val);
+ ret = read_from_ec(inhibit_reg, 1, &raw_val);
if (ret)
return ret;
- if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) ==
- OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS)
+ if ((raw_val & OXP_CHARGE_INHIBIT_MASK_ALWAYS) ==
+ OXP_CHARGE_INHIBIT_MASK_ALWAYS)
val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
- else if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) ==
- OXP_X1_CHARGE_INHIBIT_MASK_AWAKE)
+ else if ((raw_val & OXP_CHARGE_INHIBIT_MASK_AWAKE) ==
+ OXP_CHARGE_INHIBIT_MASK_AWAKE)
val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE;
else
val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
@@ -573,29 +600,41 @@ static int oxp_psy_ext_set_prop(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
+ u8 limit_reg, inhibit_reg;
long raw_val;
+ switch (board) {
+ case oxp_apex:
+ limit_reg = OXP_APEX_CHARGE_LIMIT_REG;
+ inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG;
+ break;
+ default:
+ limit_reg = OXP_X1_CHARGE_LIMIT_REG;
+ inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG;
+ break;
+ }
+
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
if (val->intval < 0 || val->intval > 100)
return -EINVAL;
- return write_to_ec(OXP_X1_CHARGE_LIMIT_REG, val->intval);
+ return write_to_ec(limit_reg, val->intval);
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
switch (val->intval) {
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
raw_val = 0;
break;
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE:
- raw_val = OXP_X1_CHARGE_INHIBIT_MASK_AWAKE;
+ raw_val = OXP_CHARGE_INHIBIT_MASK_AWAKE;
break;
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
- raw_val = OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS;
+ raw_val = OXP_CHARGE_INHIBIT_MASK_ALWAYS;
break;
default:
return -EINVAL;
}
- return write_to_ec(OXP_X1_CHARGE_INHIBIT_REG, raw_val);
+ return write_to_ec(inhibit_reg, raw_val);
default:
return -EINVAL;
}
@@ -656,6 +695,7 @@ static int oxp_pwm_enable(void)
case oxp_x1:
case oxp_g1_i:
case oxp_g1_a:
+ case oxp_apex:
return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
default:
return -EINVAL;
@@ -676,6 +716,7 @@ static int oxp_pwm_disable(void)
case oxp_x1:
case oxp_g1_i:
case oxp_g1_a:
+ case oxp_apex:
return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
default:
return -EINVAL;
@@ -696,6 +737,7 @@ static int oxp_pwm_read(long *val)
case oxp_x1:
case oxp_g1_i:
case oxp_g1_a:
+ case oxp_apex:
return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
default:
return -EOPNOTSUPP;
@@ -732,6 +774,7 @@ static int oxp_pwm_fan_speed(long *val)
case oxp_mini_amd_a07:
case oxp_mini_amd_pro:
case oxp_g1_a:
+ case oxp_apex:
return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
default:
return -EOPNOTSUPP;
@@ -764,6 +807,7 @@ static int oxp_pwm_input_write(long val)
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
+ case oxp_apex:
return write_to_ec(OXP_SENSOR_PWM_REG, val);
default:
return -EOPNOTSUPP;
@@ -803,6 +847,7 @@ static int oxp_pwm_input_read(long *val)
case oxp_fly:
case oxp_mini_amd_pro:
case oxp_g1_a:
+ case oxp_apex:
default:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
if (ret)
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 19d194ff37ca..2b1d3c2eb2e5 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -107,7 +107,7 @@
*
* Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
* - v0.4 first post version
- * add function to retrive SIFR
+ * add function to retrieve SIFR
*
* Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
* - v0.3 get proper status of hotkey
@@ -232,6 +232,7 @@ static const struct key_entry panasonic_keymap[] = {
{ KE_KEY, 41, { KEY_MACRO9 } },
{ KE_KEY, 42, { KEY_MACRO10 } },
{ KE_KEY, 43, { KEY_MACRO11 } },
+ { KE_KEY, 48, { KEY_FN } },
{ KE_END, 0 }
};
@@ -864,6 +865,17 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
}
/*
+ * Let's Note models report both FN key down and key up
+ * without the expected 0x80 key up/down bit
+ */
+ if (key == 48) {
+ updown = 1;
+ } else if (key == 49) {
+ key = 48;
+ updown = 0;
+ }
+
+ /*
* Don't report brightness key-presses if they are also reported
* by the ACPI video bus.
*/
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
index 710f3d5bf84c..18631e96bb17 100644
--- a/drivers/platform/x86/samsung-laptop.c
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -295,7 +295,7 @@ static const struct sabi_config sabi_configs[] = {
* d0, d1, d2, d3 - data fields
* call - call SABI using command and data
*
- * This allow to call arbitrary sabi commands wihout
+ * This allow to call arbitrary sabi commands without
* modifying the driver at all.
* For example, setting the keyboard backlight brightness to 5
*
diff --git a/drivers/platform/x86/siemens/simatic-ipc.c b/drivers/platform/x86/siemens/simatic-ipc.c
index 7039874d8f11..4a4509fc9263 100644
--- a/drivers/platform/x86/siemens/simatic-ipc.c
+++ b/drivers/platform/x86/siemens/simatic-ipc.c
@@ -92,6 +92,7 @@ static int register_platform_devices(u32 station_id)
u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
u8 battmode = SIMATIC_IPC_DEVICE_NONE;
char *pdevname;
+ int ret;
int i;
for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
@@ -139,8 +140,10 @@ static int register_platform_devices(u32 station_id)
pdevname, PLATFORM_DEVID_NONE,
&platform_data,
sizeof(struct simatic_ipc_platform));
- if (IS_ERR(ipc_led_platform_device))
- return PTR_ERR(ipc_led_platform_device);
+ if (IS_ERR(ipc_led_platform_device)) {
+ ret = PTR_ERR(ipc_led_platform_device);
+ goto err_unregister_batt;
+ }
pr_debug("device=%s created\n",
ipc_led_platform_device->name);
@@ -153,8 +156,10 @@ static int register_platform_devices(u32 station_id)
KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE,
&platform_data,
sizeof(struct simatic_ipc_platform));
- if (IS_ERR(ipc_wdt_platform_device))
- return PTR_ERR(ipc_wdt_platform_device);
+ if (IS_ERR(ipc_wdt_platform_device)) {
+ ret = PTR_ERR(ipc_wdt_platform_device);
+ goto err_unregister_led;
+ }
pr_debug("device=%s created\n",
ipc_wdt_platform_device->name);
@@ -169,6 +174,14 @@ static int register_platform_devices(u32 station_id)
}
return 0;
+
+err_unregister_led:
+ if (ledmode != SIMATIC_IPC_DEVICE_NONE)
+ platform_device_unregister(ipc_led_platform_device);
+err_unregister_batt:
+ if (battmode != SIMATIC_IPC_DEVICE_NONE)
+ platform_device_unregister(ipc_batt_platform_device);
+ return ret;
}
static void request_additional_modules(u32 station_id)
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
index 0e842c55dcc5..730a599abf0f 100644
--- a/drivers/platform/x86/topstar-laptop.c
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -287,10 +287,14 @@ static const struct dmi_system_id topstar_dmi_ids[] = {
static int topstar_acpi_probe(struct platform_device *pdev)
{
- struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+ struct acpi_device *device;
struct topstar_laptop *topstar;
int err;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
dmi_check_system(topstar_dmi_ids);
topstar = kzalloc_obj(struct topstar_laptop);
diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c
index c6633b74029f..b2eb91929d1d 100644
--- a/drivers/platform/x86/toshiba_haps.c
+++ b/drivers/platform/x86/toshiba_haps.c
@@ -244,18 +244,13 @@ static int toshiba_haps_suspend(struct device *device)
static int toshiba_haps_resume(struct device *device)
{
struct toshiba_haps_dev *haps = dev_get_drvdata(device);
- int ret;
/* Set the stored protection level */
- ret = toshiba_haps_protection_level(haps->acpi_dev->handle,
- haps->protection_level);
+ toshiba_haps_protection_level(haps->acpi_dev->handle,
+ haps->protection_level);
/* Reset the protection on resume */
- ret = toshiba_haps_reset_protection(haps->acpi_dev->handle);
- if (ret != 0)
- return ret;
-
- return ret;
+ return toshiba_haps_reset_protection(haps->acpi_dev->handle);
}
#endif
diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c
index 32d7756022c2..3e5ba524fe58 100644
--- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c
+++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c
@@ -1,9 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* This driver implements the WMI AB device found on TUXEDO notebooks with board
- * vendor NB04.
+ * vendor NB04. This enables keyboard backlight control via a virtual HID
+ * LampArray device.
+ *
+ * The device will be available through the regular HID interfaces, such as
+ * hidraw and can be used by any userspace program that implements the HID
+ * LampArray standard.
*
* Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com>
+ * Copyright (C) 2026 Aaron Erhardt <aer@tuxedocomputers.com>
*/
#include <linux/dmi.h>
@@ -232,8 +238,8 @@ static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = {
{ 0x2e, { 246000, 67500, 5250 } },
{ 0x2a, { 269500, 67500, 5250 } },
{ 0x53, { 294500, 67500, 5250 } },
- { 0x55, { 311200, 67500, 5250 } },
- { 0x54, { 327900, 67500, 5250 } },
+ { 0x54, { 311200, 67500, 5250 } },
+ { 0x55, { 327900, 67500, 5250 } },
{ 0x56, { 344600, 67500, 5250 } },
{ 0x2b, { 31000, 85500, 5500 } },
{ 0x14, { 51500, 85500, 5500 } },
@@ -337,8 +343,8 @@ static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = {
{ 0x2e, { 246000, 67500, 5250 } },
{ 0x2a, { 269500, 67500, 5250 } },
{ 0x53, { 294500, 67500, 5250 } },
- { 0x55, { 311200, 67500, 5250 } },
- { 0x54, { 327900, 67500, 5250 } },
+ { 0x54, { 311200, 67500, 5250 } },
+ { 0x55, { 327900, 67500, 5250 } },
{ 0x56, { 344600, 67500, 5250 } },
{ 0x2b, { 31000, 85500, 5500 } },
{ 0x14, { 51500, 85500, 5500 } },
@@ -448,7 +454,7 @@ static int tux_ll_start(struct hid_device *hdev)
}
driver_data->next_lamp_id = 0;
- dev_set_drvdata(&hdev->dev, driver_data);
+ hdev->driver_data = driver_data;
return ret;
}
@@ -485,15 +491,17 @@ struct __packed lamp_array_attributes_report_t {
static int handle_lamp_array_attributes_report(struct hid_device *hdev,
struct lamp_array_attributes_report_t *rep)
{
- struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct tux_hdev_driver_data_t *driver_data = hdev->driver_data;
rep->lamp_count = driver_data->lamp_count;
+
+ // Physical dimensions of the Sirius 16 keyboard
rep->bounding_box_width_in_micrometers = 368000;
rep->bounding_box_height_in_micrometers = 266000;
rep->bounding_box_depth_in_micrometers = 30000;
/*
* LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of
- * "HID Usage Tables v1.5"
+ * "HID Usage Tables v1.7"
*/
rep->lamp_array_kind = 1;
// Some guessed value for interval microseconds
@@ -510,7 +518,7 @@ struct __packed lamp_attributes_request_report_t {
static int handle_lamp_attributes_request_report(struct hid_device *hdev,
struct lamp_attributes_request_report_t *rep)
{
- struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct tux_hdev_driver_data_t *driver_data = hdev->driver_data;
if (rep->lamp_id < driver_data->lamp_count)
driver_data->next_lamp_id = rep->lamp_id;
@@ -539,7 +547,7 @@ struct __packed lamp_attributes_response_report_t {
static int handle_lamp_attributes_response_report(struct hid_device *hdev,
struct lamp_attributes_response_report_t *rep)
{
- struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct tux_hdev_driver_data_t *driver_data = hdev->driver_data;
u16 lamp_id = driver_data->next_lamp_id;
rep->lamp_id = lamp_id;
@@ -547,21 +555,21 @@ static int handle_lamp_attributes_response_report(struct hid_device *hdev,
rep->update_latency_in_microseconds = 100;
/*
* LampPurposeControl, see "26.3.1 LampPurposes Flags" of
- * "HID Usage Tables v1.5"
+ * "HID Usage Tables v1.7"
*/
rep->lamp_purpose = 1;
rep->red_level_count = 0xff;
rep->green_level_count = 0xff;
rep->blue_level_count = 0xff;
- rep->intensity_level_count = 0xff;
+ rep->intensity_level_count = 0x1;
rep->is_programmable = 1;
if (driver_data->kbl_map[lamp_id].code <= 0xe8) {
rep->input_binding = driver_data->kbl_map[lamp_id].code;
} else {
/*
- * Everything bigger is reserved/undefined, see
- * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5"
+ * Everything bigger than 0xe8 is reserved/undefined, see
+ * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.7"
* and should return 0, see "26.8.3 Lamp Attributes" of the same
* document.
*/
@@ -598,7 +606,7 @@ struct __packed lamp_multi_update_report_t {
static int handle_lamp_multi_update_report(struct hid_device *hdev,
struct lamp_multi_update_report_t *rep)
{
- struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct tux_hdev_driver_data_t *driver_data = hdev->driver_data;
union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in;
struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j;
struct wmi_device *wdev = to_wmi_device(hdev->dev.parent);
@@ -606,10 +614,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev,
u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i;
int ret;
- /*
- * Catching misformatted lamp_multi_update_report and fail silently
- * according to "HID Usage Tables v1.5"
- */
+ // Catch bad reports and fail silently according to "HID Usage Tables v1.7"
for (unsigned int i = 0; i < rep->lamp_count; ++i) {
if (rep->lamp_id[i] > driver_data->lamp_count) {
hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n");
@@ -624,6 +629,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev,
}
}
+ // Fill kbl_set_multiple_keys_in update buffer
for (unsigned int i = 0; i < rep->lamp_count; ++i) {
key_id = driver_data->kbl_map[rep->lamp_id[i]].code;
@@ -632,6 +638,8 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev,
++j) {
rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j];
key_id_j = rgb_configs_j->key_id;
+
+ // Search for existing or empty entry
if (key_id_j != 0x00 && key_id_j != key_id)
continue;
@@ -640,22 +648,23 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev,
j + 1;
rgb_configs_j->key_id = key_id;
/*
- * While this driver respects update_channel.intensity
- * according to "HID Usage Tables v1.5" also on RGB
- * leds, the Microsoft MacroPad reference implementation
+ * This driver uses update_channel.intensity according to
+ * "Color Attributes Examples" in "HID Usage Tables v1.7".
+ * Only two intensity values are allowed for turning LEDs
+ * on or off, while color and brightness can be controlled
+ * through the RGB values. This is also identical to the
+ * Microsoft MacroPad reference implementation
* (https://github.com/microsoft/RP2040MacropadHidSample
- * 1d6c3ad) does not and ignores it. If it turns out
- * that Windows writes intensity = 0 for RGB leds
- * instead of intensity = 255, this driver should also
- * ignore the update_channel.intensity.
+ * 1d6c3ad).
*/
- intensity_i = rep->update_channels[i].intensity;
+ intensity_i = min(1, rep->update_channels[i].intensity);
red_i = rep->update_channels[i].red;
green_i = rep->update_channels[i].green;
blue_i = rep->update_channels[i].blue;
- rgb_configs_j->red = red_i * intensity_i / 0xff;
- rgb_configs_j->green = green_i * intensity_i / 0xff;
- rgb_configs_j->blue = blue_i * intensity_i / 0xff;
+
+ rgb_configs_j->red = red_i * intensity_i;
+ rgb_configs_j->green = green_i * intensity_i;
+ rgb_configs_j->blue = blue_i * intensity_i;
break;
}
@@ -683,17 +692,14 @@ struct __packed lamp_range_update_report_t {
static int handle_lamp_range_update_report(struct hid_device *hdev,
struct lamp_range_update_report_t *rep)
{
- struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct tux_hdev_driver_data_t *driver_data = hdev->driver_data;
struct lamp_multi_update_report_t lamp_multi_update_report = {
.report_id = LAMP_MULTI_UPDATE_REPORT_ID,
};
struct lamp_rgbi_tuple_t *update_channels_j;
int ret;
- /*
- * Catching misformatted lamp_range_update_report and fail silently
- * according to "HID Usage Tables v1.5"
- */
+ // Catch bad reports and fail silently according to "HID Usage Tables v1.7"
if (rep->lamp_id_start > rep->lamp_id_end) {
hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n");
return sizeof(*rep);
@@ -705,8 +711,8 @@ static int handle_lamp_range_update_report(struct hid_device *hdev,
}
/*
- * Break handle_lamp_range_update_report call down to multiple
- * handle_lamp_multi_update_report calls to easily ensure that mixing
+ * Break handle_lamp_range_update_report call down into multiple
+ * handle_lamp_multi_update_report calls to ensure that mixing
* handle_lamp_range_update_report and handle_lamp_multi_update_report
* does not break things.
*/
@@ -749,15 +755,12 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu
struct lamp_array_control_report_t *rep)
{
/*
- * The keyboards firmware doesn't have any built in controls and the
- * built in effects are not implemented so this is a NOOP.
- * According to the HID Documentation (HID Usage Tables v1.5) this
+ * The keyboard's firmware doesn't have any built-in controls and the
+ * built-in effects are not implemented so this is a NOOP.
+ * According to the HID Documentation (HID Usage Tables v1.7) this
* function is optional and can be removed from the HID Report
* Descriptor, but it should first be confirmed that userspace respects
- * this possibility too. The Microsoft MacroPad reference implementation
- * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
- * already deviates from the spec at another point, see
- * handle_lamp_*_update_report.
+ * this possibility too.
*/
return sizeof(*rep);
@@ -766,6 +769,8 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu
static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf,
size_t len, unsigned char rtype, int reqtype)
{
+ int ret = -EINVAL;
+
if (rtype != HID_FEATURE_REPORT)
return -EINVAL;
@@ -775,13 +780,15 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf,
case LAMP_ARRAY_ATTRIBUTES_REPORT_ID:
if (len != sizeof(struct lamp_array_attributes_report_t))
return -EINVAL;
- return handle_lamp_array_attributes_report(hdev,
+ ret = handle_lamp_array_attributes_report(hdev,
(struct lamp_array_attributes_report_t *)buf);
+ break;
case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID:
if (len != sizeof(struct lamp_attributes_response_report_t))
return -EINVAL;
- return handle_lamp_attributes_response_report(hdev,
+ ret = handle_lamp_attributes_response_report(hdev,
(struct lamp_attributes_response_report_t *)buf);
+ break;
}
break;
case HID_REQ_SET_REPORT:
@@ -789,28 +796,36 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf,
case LAMP_ATTRIBUTES_REQUEST_REPORT_ID:
if (len != sizeof(struct lamp_attributes_request_report_t))
return -EINVAL;
- return handle_lamp_attributes_request_report(hdev,
+ ret = handle_lamp_attributes_request_report(hdev,
(struct lamp_attributes_request_report_t *)buf);
+ break;
case LAMP_MULTI_UPDATE_REPORT_ID:
if (len != sizeof(struct lamp_multi_update_report_t))
return -EINVAL;
- return handle_lamp_multi_update_report(hdev,
+ ret = handle_lamp_multi_update_report(hdev,
(struct lamp_multi_update_report_t *)buf);
+ break;
case LAMP_RANGE_UPDATE_REPORT_ID:
if (len != sizeof(struct lamp_range_update_report_t))
return -EINVAL;
- return handle_lamp_range_update_report(hdev,
+ ret = handle_lamp_range_update_report(hdev,
(struct lamp_range_update_report_t *)buf);
+ break;
case LAMP_ARRAY_CONTROL_REPORT_ID:
if (len != sizeof(struct lamp_array_control_report_t))
return -EINVAL;
- return handle_lamp_array_control_report(hdev,
+ ret = handle_lamp_array_control_report(hdev,
(struct lamp_array_control_report_t *)buf);
+ break;
}
break;
}
- return -EINVAL;
+ /* Set report number on success */
+ if (ret > 0)
+ buf[0] = reportnum;
+
+ return ret;
}
static const struct hid_ll_driver tux_ll_driver = {
@@ -881,8 +896,8 @@ static struct wmi_driver tuxedo_nb04_wmi_tux_driver = {
};
/*
- * We don't know if the WMI API is stable and how unique the GUID is for this
- * ODM. To be on the safe side we therefore only run this driver on tested
+ * We don't know whether the WMI API is stable and how unique the GUID is for
+ * this ODM. To be on the safe side we therefore only run this driver on tested
* devices defined by this list.
*/
static const struct dmi_system_id tested_devices_dmi_table[] __initconst = {
@@ -920,4 +935,5 @@ module_exit(tuxedo_nb04_wmi_tux_exit);
MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices");
MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
+MODULE_AUTHOR("Aaron Erhardt <aer@tuxedocomputers.com>");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/uniwill/Kconfig
index d07cc8440188..f80794bdfaef 100644
--- a/drivers/platform/x86/uniwill/Kconfig
+++ b/drivers/platform/x86/uniwill/Kconfig
@@ -28,6 +28,7 @@ config UNIWILL_LAPTOP
depends on DMI
select REGMAP
select INPUT_SPARSEKMAP
+ select LEDS_TRIGGERS
help
This driver adds support for various extra features found on Uniwill laptops,
like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index 7a2eeaec4c96..f62d59f29676 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -165,9 +165,10 @@
#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
#define LIGHTBAR_APP_EXISTS BIT(0)
#define LIGHTBAR_POWER_SAVE BIT(1)
-#define LIGHTBAR_S0_OFF BIT(2)
-#define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended
-#define LIGHTBAR_WELCOME BIT(7) // Rainbow animation
+#define LIGHTBAR_S0_OFF BIT(2) /* Also disables animations */
+#define LIGHTBAR_S3_OFF BIT(3) /* No breathing animation when in s2ram */
+#define LIGHTBAR_MODERN_STANDBY_ON BIT(6) /* Breathing animation when in s2idle */
+#define LIGHTBAR_WELCOME BIT(7) /* Rainbow animation */
#define EC_ADDR_LIGHTBAR_AC_RED 0x0749
@@ -306,7 +307,7 @@
#define EC_ADDR_USB_C_POWER_PRIORITY 0x07CC
#define USB_C_POWER_PRIORITY BIT(7)
-/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */
+/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF and LIGHTBAR_MODERN_STANDBY_ON */
#define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2
#define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3
@@ -398,7 +399,7 @@ struct uniwill_data {
struct mutex super_key_lock; /* Protects the toggling of the super key lock state */
struct list_head batteries;
struct mutex led_lock; /* Protects writes to the lightbar registers */
- u8 lightbar_max_brightness;
+ u8 lightbar_max_intensity;
struct led_classdev_mc led_mc_cdev;
struct mc_subled led_mc_subled_info[LED_CHANNELS];
bool kbd_led_single_color;
@@ -429,7 +430,7 @@ struct uniwill_device_descriptor {
unsigned int features;
bool kbd_led_single_color;
u8 kbd_led_max_brightness;
- u8 lightbar_max_brightness;
+ u8 lightbar_max_intensity;
/* Executed during driver probing */
int (*probe)(struct uniwill_data *data);
};
@@ -506,6 +507,9 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the webcam */
{ KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
+ /* Reported when the user wants to rotate the (lay-flat) screen by 180 degrees */
+ { KE_KEY, UNIWILL_OSD_ROTATE_SCREEN, { KEY_ROTATE_DISPLAY }},
+
{ KE_END }
};
@@ -645,6 +649,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_BIOS_OEM:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
+ case EC_ADDR_SUPPORT_1:
case EC_ADDR_SUPPORT_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
@@ -684,6 +689,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
case EC_ADDR_BIOS_OEM:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
+ case EC_ADDR_SUPPORT_1:
case EC_ADDR_SUPPORT_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
@@ -898,51 +904,6 @@ static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_att
static DEVICE_ATTR_RW(touchpad_toggle_enable);
-static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct uniwill_data *data = dev_get_drvdata(dev);
- unsigned int value;
- bool enable;
- int ret;
-
- ret = kstrtobool(buf, &enable);
- if (ret < 0)
- return ret;
-
- if (enable)
- value = LIGHTBAR_WELCOME;
- else
- value = 0;
-
- guard(mutex)(&data->led_lock);
-
- ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value);
- if (ret < 0)
- return ret;
-
- ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value);
- if (ret < 0)
- return ret;
-
- return count;
-}
-
-static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct uniwill_data *data = dev_get_drvdata(dev);
- unsigned int value;
- int ret;
-
- ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value);
- if (ret < 0)
- return ret;
-
- return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME));
-}
-
-static DEVICE_ATTR_RW(rainbow_animation);
-
static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -956,12 +917,13 @@ static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attr
return ret;
if (enable)
- value = 0;
+ value = LIGHTBAR_MODERN_STANDBY_ON;
else
value = LIGHTBAR_S3_OFF;
/* We only access a single register here, so we do not need to use data->led_lock */
- ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value);
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL,
+ LIGHTBAR_S3_OFF | LIGHTBAR_MODERN_STANDBY_ON, value);
if (ret < 0)
return ret;
@@ -979,6 +941,10 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri
if (ret < 0)
return ret;
+ /*
+ * We only test LIGHTBAR_S3_OFF here, because LIGHTBAR_MODERN_STANDBY_ON
+ * should have the exact opposite value.
+ */
return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF));
}
@@ -1256,7 +1222,6 @@ static struct attribute *uniwill_attrs[] = {
&dev_attr_super_key_enable.attr,
&dev_attr_touchpad_toggle_enable.attr,
/* Lightbar-related */
- &dev_attr_rainbow_animation.attr,
&dev_attr_breathing_in_suspend.attr,
/* Power-management-related */
&dev_attr_ctgp_offset.attr,
@@ -1286,8 +1251,7 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a
return attr->mode;
}
- if (attr == &dev_attr_rainbow_animation.attr ||
- attr == &dev_attr_breathing_in_suspend.attr) {
+ if (attr == &dev_attr_breathing_in_suspend.attr) {
if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
return attr->mode;
}
@@ -1509,20 +1473,16 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br
unsigned int value;
int ret;
- ret = led_mc_calc_color_components(led_mc_cdev, brightness);
- if (ret < 0)
- return ret;
-
guard(mutex)(&data->led_lock);
for (int i = 0; i < LED_CHANNELS; i++) {
- /* Prevent the brightness values from overflowing */
- value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness);
- ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i],
+ data->led_mc_subled_info[i].intensity);
if (ret < 0)
return ret;
- ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value);
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i],
+ data->led_mc_subled_info[i].intensity);
if (ret < 0)
return ret;
}
@@ -1539,13 +1499,67 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br
return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value);
}
-#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME)
+static int uniwill_led_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev);
+ unsigned int value;
+ int ret;
+
+ guard(mutex)(&data->led_lock);
+
+ if (flags)
+ value = LIGHTBAR_WELCOME;
+ else
+ value = LIGHTBAR_S0_OFF;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL,
+ LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL,
+ LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value);
+ if (ret < 0)
+ return ret;
+
+ /* The LED is always on during hw control */
+ led_cdev->brightness = flags;
+
+ return 0;
+}
+
+static int uniwill_lightbar_trigger_activate(struct led_classdev *led_cdev)
+{
+ return led_cdev->hw_control_set(led_cdev, 1);
+}
+
+static void uniwill_lightbar_trigger_deactivate(struct led_classdev *led_cdev)
+{
+ int ret;
+
+ ret = led_cdev->hw_control_set(led_cdev, 0);
+ if (ret < 0)
+ dev_err(led_cdev->dev, "Failed to stop rainbow animation: %d\n", ret);
+}
+
+static struct led_hw_trigger_type uniwill_lightbar_trigger_type;
+
+static struct led_trigger uniwill_lightbar_trigger = {
+ .name = "uniwill-rainbow",
+ .activate = uniwill_lightbar_trigger_activate,
+ .deactivate = uniwill_lightbar_trigger_deactivate,
+ .trigger_type = &uniwill_lightbar_trigger_type,
+};
+
+#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | \
+ LIGHTBAR_MODERN_STANDBY_ON | LIGHTBAR_WELCOME)
static int uniwill_led_init(struct uniwill_data *data)
{
struct led_init_data init_data = {
.devicename = DRIVER_NAME,
- .default_label = "multicolor:" LED_FUNCTION_STATUS,
+ .default_label = "rgb:" LED_FUNCTION_STATUS,
.devname_mandatory = true,
};
unsigned int color_indices[3] = {
@@ -1572,6 +1586,11 @@ static int uniwill_led_init(struct uniwill_data *data)
return ret;
value |= LIGHTBAR_APP_EXISTS;
+ if (value & LIGHTBAR_S3_OFF)
+ value &= ~LIGHTBAR_MODERN_STANDBY_ON;
+ else
+ value |= LIGHTBAR_MODERN_STANDBY_ON;
+
ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value);
if (ret < 0)
return ret;
@@ -1581,19 +1600,26 @@ static int uniwill_led_init(struct uniwill_data *data)
* running on battery power.
*/
value |= LIGHTBAR_S3_OFF;
+ value &= ~LIGHTBAR_MODERN_STANDBY_ON;
ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value);
if (ret < 0)
return ret;
- data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
- data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness;
+ data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB;
+ data->led_mc_cdev.led_cdev.max_brightness = 1;
data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT;
data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set;
+ data->led_mc_cdev.led_cdev.trigger_type = &uniwill_lightbar_trigger_type;
+ data->led_mc_cdev.led_cdev.hw_control_trigger = uniwill_lightbar_trigger.name;
+ data->led_mc_cdev.led_cdev.hw_control_set = uniwill_led_hw_control_set;
if (value & LIGHTBAR_S0_OFF)
data->led_mc_cdev.led_cdev.brightness = 0;
else
- data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness;
+ data->led_mc_cdev.led_cdev.brightness = 1;
+
+ if (value & LIGHTBAR_WELCOME)
+ data->led_mc_cdev.led_cdev.default_trigger = uniwill_lightbar_trigger.name;
for (int i = 0; i < LED_CHANNELS; i++) {
data->led_mc_subled_info[i].color_index = color_indices[i];
@@ -1604,9 +1630,9 @@ static int uniwill_led_init(struct uniwill_data *data)
/*
* Make sure that the initial intensity value is not greater than
- * the maximum brightness.
+ * the maximum intensity.
*/
- value = min(data->lightbar_max_brightness, value);
+ value = min(data->lightbar_max_intensity, value);
ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
if (ret < 0)
return ret;
@@ -1616,6 +1642,7 @@ static int uniwill_led_init(struct uniwill_data *data)
return ret;
data->led_mc_subled_info[i].intensity = value;
+ data->led_mc_subled_info[i].max_intensity = data->lightbar_max_intensity;
data->led_mc_subled_info[i].channel = i;
}
@@ -1631,6 +1658,32 @@ static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness)
struct led_classdev *led_cdev;
int ret;
+ /*
+ * The devices with 3 brightness level keyboards send
+ * UNIWILL_OSD_KB_LED_LEVEL0, UNIWILL_OSD_KB_LED_LEVEL2, and
+ * UNIWILL_OSD_KB_LED_LEVEL4 which need to be mapped to 0, 1, and 2.
+ */
+ switch (data->kbd_led_max_brightness) {
+ case 4:
+ break;
+ case 2:
+ switch (brightness) {
+ case 0:
+ break;
+ case 2:
+ brightness = 1;
+ break;
+ case 4:
+ brightness = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
if (data->kbd_led_single_color)
led_cdev = &data->kbd_led_cdev;
else
@@ -1705,7 +1758,7 @@ static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev,
/*
* The EC interprets a RGB value of 0x000000 as a command to restore
- * the device-specfic default RGB value. Work around this by writing
+ * the device-specific default RGB value. Work around this by writing
* a RGB value of 0x010101 (faint white) instead.
*/
if (data->kbd_led_mc_subled_info[0].intensity == 0 &&
@@ -1760,7 +1813,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data)
LED_COLOR_ID_BLUE,
};
struct led_init_data init_data = {
- .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT,
+ .default_label = "rgb:" LED_FUNCTION_KBD_BACKLIGHT,
.devicename = DRIVER_NAME,
.devname_mandatory = true,
};
@@ -1821,7 +1874,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data)
return ret;
data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness;
- data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
+ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB;
data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT;
data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set;
data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get;
@@ -2338,7 +2391,7 @@ static int uniwill_probe(struct platform_device *pdev)
data->features = device_descriptor.features;
data->kbd_led_single_color = device_descriptor.kbd_led_single_color;
data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness;
- data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness;
+ data->lightbar_max_intensity = device_descriptor.lightbar_max_intensity;
/*
* Some devices might need to perform some device-specific initialization steps
@@ -2685,7 +2738,7 @@ static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
- .lightbar_max_brightness = 36,
+ .lightbar_max_intensity = 36,
};
static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
@@ -2699,6 +2752,20 @@ static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
UNIWILL_FEATURE_SECONDARY_FAN,
};
+static struct uniwill_device_descriptor lapkc71e_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_LIGHTBAR |
+ UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+ .lightbar_max_intensity = 200,
+};
+
static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
@@ -2709,40 +2776,98 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
- .lightbar_max_brightness = 200,
+ .lightbar_max_intensity = 200,
};
-/*
- * The featuresets below reflect somewhat chronological changes:
- * 1 -> 2: UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL is added to the EC firmware.
- * 2 -> 3: UNIWILL_FEATURE_USB_C_POWER_PRIORITY is removed from the EC firmware.
- * Some devices might divert from this timeline.
- */
+static struct uniwill_device_descriptor pfxnuxx_pfxluxx_phxprxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
+};
-static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
+static struct uniwill_device_descriptor gkxmrxx_gkxnpxx_gkxnrxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
- UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
};
-static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = {
+static struct uniwill_device_descriptor gmxmpxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+ .lightbar_max_intensity = 36,
+};
+
+static struct uniwill_device_descriptor phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
+};
+
+static struct uniwill_device_descriptor gmxmgxx_gmxngxx_gmxxgxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
+};
+
+static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_pol_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
};
-static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = {
+static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_ste_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
@@ -2750,20 +2875,73 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini
UNIWILL_FEATURE_SECONDARY_FAN |
UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+ .lightbar_max_intensity = 36,
};
-static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = {
+static int gmxrgxx_probe(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_1, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & LIGHTBAR) {
+ data->features |= UNIWILL_FEATURE_LIGHTBAR;
+ data->lightbar_max_intensity = 36;
+ }
+
+ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & RGB_KEYBOARD) {
+ data->features |= UNIWILL_FEATURE_KEYBOARD_BACKLIGHT;
+ data->kbd_led_single_color = false;
+ data->kbd_led_max_brightness = 4;
+ }
+
+ return 0;
+};
+
+static struct uniwill_device_descriptor gmxrgxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+ .probe = gmxrgxx_probe,
+};
+
+static struct uniwill_device_descriptor gmxagxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+ .lightbar_max_intensity = 36,
};
-static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = {
+static struct uniwill_device_descriptor gmxxgxx_gmxpxxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+};
+
+static struct uniwill_device_descriptor gxxhrxx_gxxmrxx_xxxx4nxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
@@ -2771,10 +2949,29 @@ static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata =
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
UNIWILL_FEATURE_AC_AUTO_BOOT |
- UNIWILL_FEATURE_USB_POWERSHARE,
+ UNIWILL_FEATURE_USB_POWERSHARE |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 4,
};
-static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = {
+static struct uniwill_device_descriptor gmxhgxx_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
+};
+
+static struct uniwill_device_descriptor gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
@@ -2787,7 +2984,7 @@ static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __ini
UNIWILL_FEATURE_USB_POWERSHARE,
};
-static int phxtxx1_probe(struct uniwill_data *data)
+static int phxtuxx_phxtrxx_probe(struct uniwill_data *data)
{
unsigned int value;
int ret;
@@ -2802,17 +2999,20 @@ static int phxtxx1_probe(struct uniwill_data *data)
return 0;
};
-static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = {
+static struct uniwill_device_descriptor phxtuxx_phxtrxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
- UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
- .probe = phxtxx1_probe,
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
+ .probe = phxtuxx_phxtrxx_probe,
};
-static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
+static int phxarxx_phxaqxx_probe(struct uniwill_data *data)
{
unsigned int value;
int ret;
@@ -2828,22 +3028,28 @@ static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
return 0;
};
-static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
+static struct uniwill_device_descriptor phxarxx_phxaqxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
- UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
- .probe = phxarx1_phxaqf1_probe,
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
+ .probe = phxarxx_phxaqxx_probe,
};
-static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = {
+static struct uniwill_device_descriptor pfxpuxx_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_CPU_TEMP |
- UNIWILL_FEATURE_PRIMARY_FAN,
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
};
static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = {
@@ -2928,6 +3134,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
.ident = "Intel NUC x15",
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71E"),
+ },
+ .driver_data = &lapkc71e_descriptor,
+ },
+ {
+ .ident = "Intel NUC x15",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
},
.driver_data = &lapkc71f_descriptor,
@@ -2938,7 +3152,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
},
- .driver_data = &phxtxx1_descriptor,
+ .driver_data = &phxtuxx_phxtrxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -2946,7 +3160,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
@@ -2954,7 +3168,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
},
- .driver_data = &phxarx1_phxaqf1_descriptor,
+ .driver_data = &phxarxx_phxaqxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7",
@@ -2962,7 +3176,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -2970,7 +3184,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
},
- .driver_data = &tux_featureset_1_descriptor,
+ .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -2978,7 +3192,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
@@ -2986,7 +3200,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
@@ -2994,7 +3208,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
},
- .driver_data = &tux_featureset_4_descriptor,
+ .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
@@ -3002,7 +3216,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
},
- .driver_data = &tux_featureset_4_descriptor,
+ .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -3010,7 +3224,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
},
- .driver_data = &tux_featureset_4_descriptor,
+ .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -3018,7 +3232,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
},
- .driver_data = &tux_featureset_4_descriptor,
+ .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -3026,7 +3240,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
},
- .driver_data = &tux_featureset_4_descriptor,
+ .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -3034,7 +3248,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -3042,7 +3256,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -3050,7 +3264,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -3058,7 +3272,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -3066,7 +3280,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -3074,7 +3288,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -3082,7 +3296,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -3090,7 +3304,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -3098,7 +3312,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -3106,7 +3320,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -3114,7 +3328,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -3122,7 +3336,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -3130,7 +3344,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor,
},
{
.ident = "TUXEDO Trinity 15 Intel Gen1",
@@ -3138,7 +3352,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gmxmpxx_descriptor,
},
{
.ident = "TUXEDO Trinity 17 Intel Gen1",
@@ -3146,7 +3360,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
},
- .driver_data = &tux_featureset_1_nvidia_descriptor,
+ .driver_data = &gmxmpxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 AMD",
@@ -3154,7 +3368,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 Intel",
@@ -3162,23 +3376,39 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15/17 Gen3 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XA03"),
+ },
+ .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15/17 Gen3 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XI03"),
+ },
+ .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor,
},
{
- .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
+ .ident = "TUXEDO Stellaris 15/17 Gen3 AMD",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XA03"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor,
},
{
- .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
+ .ident = "TUXEDO Stellaris 15/17 Gen3 Intel",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XI03"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
@@ -3186,7 +3416,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &gmxrgxx_descriptor,
},
{
.ident = "TUXEDO Stellaris 15 Gen4 Intel",
@@ -3194,7 +3424,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &gmxagxx_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen5 AMD",
@@ -3202,7 +3432,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
},
- .driver_data = &tux_featureset_2_nvidia_descriptor,
+ .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen5 AMD",
@@ -3210,7 +3440,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &gmxxgxx_gmxpxxx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5",
@@ -3218,7 +3448,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &gmxxgxx_gmxpxxx_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
@@ -3226,7 +3456,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxhgxx_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
@@ -3234,7 +3464,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -3242,7 +3472,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -3250,7 +3480,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -3258,7 +3488,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -3266,7 +3496,15 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
+ },
+ {
+ .ident = "PCSpecialist Recoil 16 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR57TY"),
+ },
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -3274,7 +3512,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -3282,7 +3520,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
},
- .driver_data = &tux_featureset_4_nvidia_descriptor,
+ .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor,
},
{
.ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -3290,7 +3528,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
},
- .driver_data = &pf5pu1g_descriptor,
+ .driver_data = &pfxpuxx_descriptor,
},
{
.ident = "TUXEDO Pulse 14 Gen1 AMD",
@@ -3298,7 +3536,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
},
- .driver_data = &tux_featureset_1_descriptor,
+ .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen1 AMD",
@@ -3306,7 +3544,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
},
- .driver_data = &tux_featureset_1_descriptor,
+ .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen2 AMD",
@@ -3314,7 +3552,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
},
- .driver_data = &tux_featureset_1_descriptor,
+ .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor,
},
{ }
};
@@ -3355,22 +3593,36 @@ static int __init uniwill_init(void)
device_descriptor.kbd_led_single_color = false;
/* Some models only support 3 brightness levels */
device_descriptor.kbd_led_max_brightness = 4;
- /* Some models only support 36 brightness levels per color component */
- device_descriptor.lightbar_max_brightness = 200;
+ /* Some models only support 36 intensity levels per color component */
+ device_descriptor.lightbar_max_intensity = 200;
pr_warn("Enabling potentially unsupported features\n");
}
- ret = platform_driver_register(&uniwill_driver);
+ /*
+ * We cannot register the trigger inside the .probe callback of the
+ * platform driver, because each trigger needs a unique name.
+ */
+ ret = led_trigger_register(&uniwill_lightbar_trigger);
if (ret < 0)
return ret;
+ ret = platform_driver_register(&uniwill_driver);
+ if (ret < 0)
+ goto err_platform;
+
ret = uniwill_wmi_register_driver();
- if (ret < 0) {
- platform_driver_unregister(&uniwill_driver);
- return ret;
- }
+ if (ret < 0)
+ goto err_wmi;
return 0;
+
+err_wmi:
+ platform_driver_unregister(&uniwill_driver);
+
+err_platform:
+ led_trigger_unregister(&uniwill_lightbar_trigger);
+
+ return ret;
}
module_init(uniwill_init);
@@ -3378,6 +3630,7 @@ static void __exit uniwill_exit(void)
{
uniwill_wmi_unregister_driver();
platform_driver_unregister(&uniwill_driver);
+ led_trigger_unregister(&uniwill_lightbar_trigger);
}
module_exit(uniwill_exit);
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
index b25b2f31211c..1b6f650c40e5 100644
--- a/drivers/platform/x86/uniwill/uniwill-wmi.h
+++ b/drivers/platform/x86/uniwill/uniwill-wmi.h
@@ -117,6 +117,9 @@
#define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
+/* "Sc" key next to F12 on the PCSpecialist Recoil 16 / Stellaris 16 Gen7 */
+#define UNIWILL_OSD_ROTATE_SCREEN 0xD0
+
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
struct device;
diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h
index 843cda8f8644..8686e79c4f75 100644
--- a/include/linux/intel_vsec.h
+++ b/include/linux/intel_vsec.h
@@ -90,13 +90,25 @@ enum intel_vsec_quirks {
* @read_telem: when specified, called by client driver to access PMT
* data (instead of direct copy).
* * dev: device reference for the callback's use
- * * guid: ID of data to acccss
+ * * guid: ID of data to access
* * data: buffer for the data to be copied
* * off: offset into the requested buffer
* * count: size of buffer
+ * @read_reg: when specified called by client driver to read PMT state
+ * * dev: device reference for the callback's use
+ * * guid: ID of data to access
+ * * reg_data: register data
+ * * offset: offset of register to read
+ * @write_reg: when specified called by client driver to write PMT state
+ * * dev: device reference for the callback's use
+ * * guid: ID of data to access
+ * * reg_data: register data
+ * * offset: offset of register to write
*/
struct pmt_callbacks {
int (*read_telem)(struct device *dev, u32 guid, u64 *data, loff_t off, u32 count);
+ int (*read_reg)(struct device *dev, u32 guid, u32 *reg_data, u32 offset);
+ int (*write_reg)(struct device *dev, u32 guid, u32 reg_data, u32 offset);
};
struct vsec_feature_dependency {
@@ -221,8 +233,7 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device
#if IS_ENABLED(CONFIG_INTEL_VSEC)
int intel_vsec_register(struct device *dev,
const struct intel_vsec_platform_info *info);
-int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
- struct intel_vsec_device *vsec_dev);
+int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct device *dev);
struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev);
#else
static inline int intel_vsec_register(struct device *dev,
@@ -231,7 +242,7 @@ static inline int intel_vsec_register(struct device *dev,
return -ENODEV;
}
static inline int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
- struct intel_vsec_device *vsec_dev)
+ struct device *dev)
{
return -ENODEV;
}
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index b5ed8c83ace1..66c7ab35d6c8 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -54,6 +54,7 @@
#define ASUS_WMI_DEVID_LED5 0x00020015
#define ASUS_WMI_DEVID_LED6 0x00020016
#define ASUS_WMI_DEVID_MICMUTE_LED 0x00040017
+#define ASUS_WMI_DEVID_MUTE_LED 0x0004001C
/* Disable Camera LED */
#define ASUS_WMI_DEVID_CAMERA_LED_NEG 0x00060078 /* 0 = on (unused) */
@@ -138,10 +139,14 @@
/* dgpu on/off */
#define ASUS_WMI_DEVID_DGPU 0x00090020
+/* dgpu on/off - alternative to ASUS_WMI_DEVID_DGPU */
+#define ASUS_WMI_DEVID_GPU_MODE 0x00090120
+
#define ASUS_WMI_DEVID_APU_MEM 0x000600C1
-#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099
+#define ASUS_WMI_DEVID_DGPU_POWER_STATE 0x00120097
#define ASUS_WMI_DEVID_DGPU_SET_TGP 0x00120098
+#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099
/* gpu mux switch, 0 = dGPU, 1 = Optimus */
#define ASUS_WMI_DEVID_GPU_MUX 0x00090016
@@ -159,7 +164,7 @@
#define ASUS_WMI_DEVID_TUF_RGB_MODE2 0x0010005A
/* TUF laptop RGB power/state */
-#define ASUS_WMI_DEVID_TUF_RGB_STATE 0x00100057
+#define ASUS_WMI_DEVID_TUF_RGB_CMD 0x00100057
/* Bootup sound control */
#define ASUS_WMI_DEVID_BOOT_SOUND 0x00130022
diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h
index ee84087d4b26..a2eba454004f 100644
--- a/include/linux/string_choices.h
+++ b/include/linux/string_choices.h
@@ -65,6 +65,12 @@ static inline const char *str_read_write(bool v)
}
#define str_write_read(v) str_read_write(!(v))
+static inline const char *str_supported_unsupported(bool v)
+{
+ return v ? "supported" : "unsupported";
+}
+#define str_unsupported_supported(v) str_supported_unsupported(!(v))
+
static inline const char *str_true_false(bool v)
{
return v ? "true" : "false";
diff --git a/include/linux/wmi.h b/include/linux/wmi.h
index defcb624a7e2..3dd223f2ff18 100644
--- a/include/linux/wmi.h
+++ b/include/linux/wmi.h
@@ -13,6 +13,7 @@
#include <linux/acpi.h>
#include <linux/device-id/wmi.h>
#include <linux/types.h>
+#include <linux/uuid.h>
/**
* struct wmi_device - WMI device structure
@@ -83,6 +84,8 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct
u8 wmidev_instance_count(struct wmi_device *wdev);
+bool wmidev_exists(const guid_t *guid);
+
/**
* struct wmi_driver - WMI driver structure
* @driver: Driver model structure
diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h
index 24cf9926dc59..151f264cd7d6 100644
--- a/include/uapi/linux/amd-pmf.h
+++ b/include/uapi/linux/amd-pmf.h
@@ -199,6 +199,9 @@ struct amd_pmf_info {
/* BIOS parameters */
__u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX];
__u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX];
+
+ /* align to 64 bit boundary */
+ __u32 __pad;
};
#endif /* _UAPI_LINUX_AMD_PMF_H */
diff --git a/tools/power/x86/intel-speed-select/Makefile b/tools/power/x86/intel-speed-select/Makefile
index 6b299aae2ded..6f896f40c1a8 100644
--- a/tools/power/x86/intel-speed-select/Makefile
+++ b/tools/power/x86/intel-speed-select/Makefile
@@ -52,8 +52,8 @@ $(OUTPUT)intel-speed-select: $(ISST_IN)
clean:
rm -f $(ALL_PROGRAMS)
- rm -rf $(OUTPUT)include/linux/isst_if.h
- find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete
+ rm -rf $(OUTPUT)include/linux/isst_if.h $(OUTPUT)include/linux/thermal.h
+ find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '.*.o.d' -delete -o -name '.*.o.cmd' -delete
install: $(ALL_PROGRAMS)
install -d -m 755 $(DESTDIR)$(bindir); \
diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c
index 2faff1aead52..c0e9e5f81337 100644
--- a/tools/power/x86/intel-speed-select/isst-config.c
+++ b/tools/power/x86/intel-speed-select/isst-config.c
@@ -16,7 +16,7 @@ struct process_cmd_struct {
int arg;
};
-static const char *version_str = "v1.26";
+static const char *version_str = "v1.27";
static const int supported_api_ver = 3;
static struct isst_if_platform_info isst_platform_info;
@@ -1545,7 +1545,7 @@ display_result:
struct isst_pkg_ctdp_level_info ctdp_level;
/* Wait for updated base frequencies */
- usleep(2000);
+ usleep(10000);
/* Adjusting uncore freq */
if (!is_dmr_plus_platform())
diff --git a/tools/power/x86/intel-speed-select/isst-core-mbox.c b/tools/power/x86/intel-speed-select/isst-core-mbox.c
index c81ecd602bcf..baadeb75376c 100644
--- a/tools/power/x86/intel-speed-select/isst-core-mbox.c
+++ b/tools/power/x86/intel-speed-select/isst-core-mbox.c
@@ -561,7 +561,7 @@ static int mbox_get_get_trl(struct isst_id *id, int level, int avx_level, int *t
if (ret)
return ret;
- debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT req:%x resp:%x\n", id->cpu,
+ debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT_RATIOS req:%x resp:%x\n", id->cpu,
req, resp);
trl[4] = resp & GENMASK(7, 0);
diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h
index 960f647cfc2d..2e66bbf34791 100644
--- a/tools/power/x86/intel-speed-select/isst.h
+++ b/tools/power/x86/intel-speed-select/isst.h
@@ -30,7 +30,7 @@
#include <linux/isst_if.h>
-#define BIT(x) (1 << (x))
+#define BIT(x) (1U << (x))
#define BIT_ULL(nr) (1ULL << (nr))
#define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (sizeof(long) * 8 - 1 - (h))))
#define GENMASK_ULL(h, l) \