summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-08-21 14:22:44 +0100
committerMark Brown <broonie@kernel.org>2026-08-21 14:22:44 +0100
commit3e3cc267d59908b5fba5b80a0601c06849cdfabb (patch)
tree5744f69abe1b5dfb851f4582e5b4b21de47f3c70
parent41dbf90938bb6bc7f745497c64915e0a4fa510a8 (diff)
parent54745d563114b74f6fecebce68cd020d06c1772b (diff)
downloadlinux-next-3e3cc267d59908b5fba5b80a0601c06849cdfabb.tar.gz
linux-next-3e3cc267d59908b5fba5b80a0601c06849cdfabb.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
-rw-r--r--Documentation/ABI/testing/amdpmf-interface73
-rw-r--r--Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock19
-rw-r--r--Documentation/ABI/testing/sysfs-driver-uniwill-laptop25
-rw-r--r--Documentation/admin-guide/laptops/thinkpad-acpi.rst24
-rw-r--r--Documentation/admin-guide/laptops/uniwill-laptop.rst27
-rw-r--r--Documentation/arch/x86/amd_hsmp.rst35
-rw-r--r--MAINTAINERS17
-rw-r--r--arch/x86/include/uapi/asm/amd_hsmp.h222
-rw-r--r--arch/x86/platform/olpc/olpc-xo15-sci.c5
-rw-r--r--drivers/char/sonypi.c2
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c13
-rw-r--r--drivers/platform/arm64/qcom-hamoa-ec.c4
-rw-r--r--drivers/platform/mellanox/mlxbf-bootctl.c1
-rw-r--r--drivers/platform/mellanox/mlxbf-pmc.c10
-rw-r--r--drivers/platform/surface/surface_acpi_notify.c6
-rw-r--r--drivers/platform/surface/surface_aggregator_hub.c9
-rw-r--r--drivers/platform/surface/surface_aggregator_tabletsw.c9
-rw-r--r--drivers/platform/surface/surfacepro3_button.c8
-rw-r--r--drivers/platform/x86/Kconfig1
-rw-r--r--drivers/platform/x86/acer-wmi.c4
-rw-r--r--drivers/platform/x86/amd/Kconfig11
-rw-r--r--drivers/platform/x86/amd/Makefile1
-rw-r--r--drivers/platform/x86/amd/amd_halo_led.c315
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c221
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c304
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h19
-rw-r--r--drivers/platform/x86/amd/hsmp/hwmon.c3
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c38
-rw-r--r--drivers/platform/x86/amd/pmc/mp1_stb.c83
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c8
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c9
-rw-r--r--drivers/platform/x86/amd/pmf/Kconfig10
-rw-r--r--drivers/platform/x86/amd/pmf/Makefile4
-rw-r--r--drivers/platform/x86/amd/pmf/core.c261
-rw-r--r--drivers/platform/x86/amd/pmf/metrics.c330
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h270
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c72
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c13
-rw-r--r--drivers/platform/x86/amd/pmf/util.c153
-rw-r--r--drivers/platform/x86/asus-armoury.c66
-rw-r--r--drivers/platform/x86/asus-armoury.h150
-rw-r--r--drivers/platform/x86/asus-laptop.c9
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c1
-rw-r--r--drivers/platform/x86/asus-wireless.c11
-rw-r--r--drivers/platform/x86/asus-wmi.c30
-rw-r--r--drivers/platform/x86/dell/dell-smbios-base.c6
-rw-r--r--drivers/platform/x86/dell/dell-smbios-smm.c8
-rw-r--r--drivers/platform/x86/dell/dell-smbios-wmi.c101
-rw-r--r--drivers/platform/x86/dell/dell-smbios.h4
-rw-r--r--drivers/platform/x86/dell/dell-wmi-base.c19
-rw-r--r--drivers/platform/x86/dell/dell-wmi-ddv.c22
-rw-r--r--drivers/platform/x86/dell/dell-wmi-privacy.c4
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c1
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h2
-rw-r--r--drivers/platform/x86/eeepc-laptop.c7
-rw-r--r--drivers/platform/x86/fujitsu-laptop.c18
-rw-r--r--drivers/platform/x86/fujitsu-tablet.c13
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.c18
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.h8
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c15
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/int-attributes.c6
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c14
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c19
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c4
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/string-attributes.c6
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c421
-rw-r--r--drivers/platform/x86/huawei-wmi.c99
-rw-r--r--drivers/platform/x86/intel/int1092/intel_sar.c32
-rw-r--r--drivers/platform/x86/intel/ishtp_eclite.c5
-rw-r--r--drivers/platform/x86/intel/pmc/core.c2
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c80
-rw-r--r--drivers/platform/x86/lenovo/Kconfig14
-rw-r--r--drivers/platform/x86/lenovo/Makefile1
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c2
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.c19
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c131
-rw-r--r--drivers/platform/x86/lenovo/yb9-kbdock.c323
-rw-r--r--drivers/platform/x86/lenovo/ymc.c27
-rw-r--r--drivers/platform/x86/lg-laptop.c644
-rw-r--r--drivers/platform/x86/msi-ec.c82
-rw-r--r--drivers/platform/x86/msi-wmi.c97
-rw-r--r--drivers/platform/x86/oxpec.c7
-rw-r--r--drivers/platform/x86/panasonic-laptop.c15
-rw-r--r--drivers/platform/x86/redmi-wmi.c26
-rw-r--r--drivers/platform/x86/samsung-galaxybook.c1
-rw-r--r--drivers/platform/x86/sony-laptop.c5
-rw-r--r--drivers/platform/x86/topstar-laptop.c2
-rw-r--r--drivers/platform/x86/toshiba_acpi.c6
-rw-r--r--drivers/platform/x86/toshiba_bluetooth.c6
-rw-r--r--drivers/platform/x86/toshiba_haps.c4
-rw-r--r--drivers/platform/x86/uniwill/uniwill-acpi.c682
-rw-r--r--drivers/platform/x86/uniwill/uniwill-wmi.h2
-rw-r--r--drivers/platform/x86/xo15-ebook.c8
-rw-r--r--drivers/power/supply/surface_battery.c11
-rw-r--r--drivers/power/supply/surface_charger.c7
-rw-r--r--include/linux/amd-pmf-io.h11
-rw-r--r--include/linux/platform_data/x86/asus-wmi.h5
-rw-r--r--include/uapi/linux/amd-pmf.h204
-rw-r--r--tools/platform/x86/amd/Makefile60
-rw-r--r--tools/platform/x86/amd/test-pmf.c142
100 files changed, 5344 insertions, 1040 deletions
diff --git a/Documentation/ABI/testing/amdpmf-interface b/Documentation/ABI/testing/amdpmf-interface
new file mode 100644
index 000000000000..625f90a013d0
--- /dev/null
+++ b/Documentation/ABI/testing/amdpmf-interface
@@ -0,0 +1,73 @@
+What: /dev/amdpmf_interface
+Date: June 2026
+KernelVersion: 7.2
+Contact: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+Description:
+ The AMD Platform Management Framework (PMF) util layer exposes a
+ minimal user-space interface via a character device for feature
+ discovery and metrics monitoring.
+
+ When CONFIG_AMD_PMF_UTIL_SUPPORT is enabled, the driver creates
+ a character device:
+
+ ======================
+ /dev/amdpmf_interface
+ ======================
+
+ The interface supports a single ioctl:
+
+ ============================ =======================================
+ IOCTL Usage
+ IOCTL_AMD_PMF_POPULATE_DATA User passes a struct amd_pmf_info with
+ the size field set to sizeof(struct
+ amd_pmf_info). The driver returns all
+ available metrics and feature status
+ in the structure.
+ ============================ =======================================
+
+ struct amd_pmf_info layout:
+
+ ======================= ========== ================================
+ Field Type Description
+ size __u64 Structure size for versioning
+ features_supported __u32 Bitmask of supported features
+ platform_type __u32 Platform form factor orientation
+ power_source __u32 AC/DC power source
+ laptop_placement __u32 Device placement state
+ lid_state __u32 Lid open/closed status
+ user_presence __u32 User presence detection
+ slider_position __u32 Current power slider position
+ skin_temp __s32 Skin temperature (centidegrees)
+ gfx_busy __u32 Graphics workload percentage
+ ambient_light __s32 Ambient light sensor reading
+ avg_c0_residency __u32 Average C0 state residency
+ max_c0_residency __u32 Maximum C0 state residency
+ socket_power __u32 Socket power consumption
+ bios_input[10] __u32 Custom BIOS input parameters
+ bios_output[10] __u32 Custom BIOS output parameters
+ ======================= ========== ================================
+
+ Feature Support Flags (features_supported bitmask):
+
+ ===================================== ==== =============================
+ Flag Bit Description
+ AMD_PMF_FEAT_AUTO_MODE 0 Auto Mode feature support
+ AMD_PMF_FEAT_STATIC_POWER_SLIDER 1 Static Power Slider support
+ AMD_PMF_FEAT_POLICY_BUILDER 2 Policy Builder (Smart PC)
+ AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC 3 Dynamic slider on AC
+ AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC 4 Dynamic slider on DC
+ ===================================== ==== =============================
+
+ Return codes:
+
+ ============= ============================================================
+ Return code Description
+ 0 Success
+ EINVAL Invalid size or parameter
+ EFAULT copy_to_user/copy_from_user failures
+ ENODEV PMF device not available
+ ENOTTY Unknown ioctl command
+ ============= ============================================================
+
+ User-space tools integrating with AMD PMF to discover capabilities and
+ monitor real-time metrics for thermal and power management validation.
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 000000000000..ebe713aefad1
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,19 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: June 2027
+KernelVersion: 7.3
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == =============================================================
+ 0 keyboard is not docked to any screen (detached)
+ 1 keyboard docked on the top half of the bottom screen
+ 2 keyboard docked on the bottom half of the bottom screen
+ == =============================================================
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
index 2397c65c969a..943f92c6b561 100644
--- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
+++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop
@@ -78,3 +78,28 @@ Description:
Reading this file returns the profile names with the currently active one in
brackets.
+
+What: /sys/bus/platform/devices/INOU0000:XX/ac_auto_boot
+Date: March 2026
+KernelVersion: 7.1
+Contact: Armin Wolf <W_Armin@gmx.de>
+Description:
+ Allows userspace applications to configure if the device should boot automatically
+ when being connected to a power source. Writing "1"/"0" into this file
+ enables/disables this functionality. Enabling both AC auto boot and USB powershare
+ at the same time is not supported.
+
+ Reading this file returns the current status of the AC auto boot functionality.
+
+What: /sys/bus/platform/devices/INOU0000:XX/usb_powershare_high
+Date: March 2026
+KernelVersion: 7.1
+Contact: Armin Wolf <W_Armin@gmx.de>
+Description:
+ Allows userspace applications to configure if the device should continue to provide
+ power via the USB ports when hibernating or powered off. Might also increase the
+ power budget available to USB ports on some devices. Writing "1"/"0" into this
+ file enables/disables this functionality. Enabling both USB powershare and AC auto
+ boot at the same time is not supported.
+
+ Reading this file returns the current status of the USB powershare functionality.
diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
index f874db31801d..db4588af0278 100644
--- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
+++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
@@ -1543,6 +1543,30 @@ Values:
This setting can also be toggled via the Fn+doubletap hotkey.
+USB-C Security
+--------------
+
+sysfs: usb_c_security
+
+Reports the current state of the USB-C Security (Restricted Mode) feature
+on supported ThinkPad systems. When enabled, USB-C data connections are
+disabled while power delivery is preserved.
+
+The available command is::
+
+ cat /sys/devices/platform/thinkpad_acpi/usb_c_security
+
+Values:
+
+ * ``enabled`` - USB-C data connections are currently blocked
+ * ``disabled`` - USB-C data connections are currently allowed
+
+The attribute is read-only. The USB-C Security state can only be toggled
+via the Fn+U followed by Fn+S hotkey chord.
+
+The sysfs attribute is not created on platforms that do not support this
+feature.
+
Auxmac
------
diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst
index 24b41dbab886..be50b45b82ef 100644
--- a/Documentation/admin-guide/laptops/uniwill-laptop.rst
+++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst
@@ -77,6 +77,19 @@ LED class device. The default name of this LED class device is ``uniwill:multico
See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various
animation modes of the lightbar.
+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 RGB colors. The maximum intensity for each color channel
+in RGB mode is 50.
+
+Keep in mind that due to hardware design choices, the driver does not support the RGB value
+``0x000000`` (black), instead it will fall back to ``0x010101`` (faint white). In order to
+disable the keyboard backlight, the standard LED brightness setting has to be used instead.
+
Configurable TGP
----------------
@@ -85,6 +98,20 @@ allow it.
See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
+AC Auto Boot
+------------
+
+The ``uniwill-laptop`` driver allows the user to configure if the system should automatically
+boot when being connected to a power source, see
+Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
+
+USB Powershare
+--------------
+
+The ``uniwill-laptop`` driver allows the user to configure if the system should continue to
+provide power via the USB ports when hibernating or powered off, see
+Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
+
References
==========
diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst
index 8bb411f0d70d..fa1fc240e212 100644
--- a/Documentation/arch/x86/amd_hsmp.rst
+++ b/Documentation/arch/x86/amd_hsmp.rst
@@ -68,6 +68,13 @@ under per socket sysfs directory created at
Note: lseek() is not supported as entire metrics table is read.
+The sysfs metrics_bin path supports only HSMP protocol version 6 and,
+because it is a file read, can return a torn snapshot if userspace
+reads in pieces. The protocol version 7 metric table (~13 KB) also
+exceeds PAGE_SIZE, so a read returns ``-EOPNOTSUPP`` there. For
+atomic reads on any protocol version, use the
+``HSMP_IOCTL_GET_TELEMETRY_DATA`` ioctl on /dev/hsmp (see below).
+
Metrics table definitions will be documented as part of Public PPR.
The same is defined in the amd_hsmp.h header.
@@ -167,7 +174,7 @@ Next thing, open the device file, as follows::
exit(1);
}
-The following IOCTL is defined:
+The following IOCTLs are defined:
``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)``
The argument is a pointer to a::
@@ -180,6 +187,32 @@ The following IOCTL is defined:
__u16 sock_ind; /* socket number */
};
+``ioctl(file, HSMP_IOCTL_GET_TELEMETRY_DATA, struct hsmp_telemetry_data *req)``
+ Atomically fetch the firmware metric (telemetry) table for a socket.
+ The ioctl copies the table in one shot, so unlike the metrics_bin
+ sysfs path it cannot return a torn snapshot and is not bounded by
+ PAGE_SIZE. Required for HSMP protocol version 7+ (e.g. Family 1Ah
+ Model 50h-5Fh, whose table is ~13 KB). Argument::
+
+ struct hsmp_telemetry_data {
+ __u64 buf; /* User pointer to destination buffer */
+ __u32 size; /* Size of @buf in bytes */
+ __u16 sock_ind; /* Socket index */
+ __u16 reserved; /* Reserved, must be zero */
+ };
+
+ ``size`` must be non-zero and no larger than the table size firmware
+ reports for that socket; a larger value is rejected with ``-EINVAL``
+ rather than short-written, and a smaller one returns the leading
+ ``size`` bytes of the snapshot. A non-zero ``reserved`` is also
+ rejected with ``-EINVAL``.
+
+ The table layout depends on the protocol version, which userspace
+ reads from the ``protocol_version`` sysfs attribute. On version 6
+ the table is ``struct hsmp_metric_table``, so callers pass
+ ``sizeof(struct hsmp_metric_table)``. Later version metrics table
+ layout is documented in the Public PPR.
+
The ioctl would return a non-zero on failure; you can read errno to see
what happened. The transaction returns 0 on success.
diff --git a/MAINTAINERS b/MAINTAINERS
index efa6145612e7..f74efaff55f2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1145,6 +1145,13 @@ F: drivers/char/hw_random/geode-rng.c
F: drivers/crypto/geode*
F: drivers/video/fbdev/geode/
+AMD HALO BOX RGB LED DRIVER
+M: Mario Limonciello (AMD) <superm1@kernel.org>
+R: Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>
+L: platform-driver-x86@vger.kernel.org
+S: Supported
+F: drivers/platform/x86/amd/amd_halo_led.c
+
AMD HSMP DRIVER
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
R: Carlos Bilbao <carlos.bilbao@kernel.org>
@@ -1263,6 +1270,7 @@ L: platform-driver-x86@vger.kernel.org
S: Supported
F: Documentation/ABI/testing/sysfs-amd-pmf
F: drivers/platform/x86/amd/pmf/
+F: include/uapi/linux/amd-pmf.h
AMD POWERPLAY AND SWSMU
M: Kenneth Feng <kenneth.feng@amd.com>
@@ -13364,7 +13372,7 @@ F: drivers/platform/x86/intel/punit_ipc.c
INTEL PMC CORE DRIVER
M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com>
-M: David E Box <david.e.box@intel.com>
+M: Xi Pardee <xi.pardee@linux.intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-platform-intel-pmc
@@ -14801,6 +14809,13 @@ F: drivers/hid/hid-lenovo-go-s.c
F: drivers/hid/hid-lenovo-go.c
F: drivers/hid/hid-lenovo.c
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
+
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h
index 92d8f256d096..eda336bfd3e9 100644
--- a/arch/x86/include/uapi/asm/amd_hsmp.h
+++ b/arch/x86/include/uapi/asm/amd_hsmp.h
@@ -53,9 +53,21 @@ enum hsmp_message_ids {
HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */
HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */
HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */
+ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */
+ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */
HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */
HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */
HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */
+ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */
+ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */
+ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */
+ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */
+ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */
+ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */
+ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */
+ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */
+ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */
+ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */
HSMP_MSG_ID_MAX,
};
@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{0, 1, HSMP_GET},
/*
- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0
- * input: args[0] = min link width[15:8] + max link width[7:0]
+ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) +
+ * min link width[15:8] + max link width[7:0]
+ * Link width encoding: 0 = x4, 1 = x8, 2 = x16.
+ * On SET, max must be >= min. On GET, [15:0] are reserved.
+ * output: args[0] = reserved[31:16] + min link width[15:8] +
+ * max link width[7:0]
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/*
- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0
- * input: args[0] = df pstate[7:0]
+ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1
+ * input: args[0] = set APB_DISABLE / get APB state[31]
+ * (0 = set & lock DF P-state, 1 = get) +
+ * reserved[30:8] +
+ * DF P-state[7:0] (0..2; reserved on GET)
+ * output: args[0] = reserved[31:9] +
+ * APB state[8] (1 = disabled, 0 = enabled) +
+ * locked DF P-state[7:0] if [8] = 1, else reserved
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */
{0, 0, HSMP_SET},
@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{1, 1, HSMP_SET},
/*
- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0
- * input: args[0] = power efficiency mode[2:0]
+ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection),
+ * num_args = 1, response_sz = 1
+ * input: args[0] = set/get policy[31] (0 = set, 1 = get) +
+ * high util point[30:24] +
+ * low util point[23:17] +
+ * PPT limit[16:5] +
+ * reserved[4:3] + mode selection[2:0]
+ * [30:5] are valid only when [2:0] is a balanced core mode
+ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1).
+ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated
+ * current efficiency mode.
*/
{1, 1, HSMP_SET_GET},
/*
- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0
- * input: args[0] = min df pstate[15:8] + max df pstate[7:0]
+ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) +
+ * reserved[30:16] +
+ * min DF P-state[15:8] + max DF P-state[7:0]
+ * DF P-state encoding: 0 = DFP0 (high performance),
+ * 1 = DFP1, 2 = DFP2 (low performance).
+ * [15:0] are reserved when getting (args[0] bit[31] = 1).
+ * output: args[0] = reserved[31:16] + min DF P-state[15:8] +
+ * max DF P-state[7:0]
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/*
* HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1
@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{0, 0, HSMP_GET},
/*
- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2
+ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3
* output: args[0] = lower 32 bits of the address
* output: args[1] = upper 32 bits of the address
+ * output: args[2] = DRAM region size in bytes
*/
- {0, 2, HSMP_GET},
+ {0, 3, HSMP_GET},
/*
* HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0
@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
*/
{1, 1, HSMP_SET_GET},
- /* RESERVED(0x29-0x2f) */
- {0, 0, HSMP_RSVD},
- {0, 0, HSMP_RSVD},
+ /*
+ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) +
+ * reserved[30:1] +
+ * enable PC6[0] (0 = disable, 1 = enable;
+ * reserved on GET)
+ * output: args[0] = reserved[31:1] + current PC6 control[0]
+ * (last value configured via HSMP or APML)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1
+ * Configures CC6 enable for all cores; changing the setting does
+ * not by itself transition cores in or out of CC6.
+ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) +
+ * reserved[30:1] +
+ * enable CC6[0] (0 = disable, 1 = enable;
+ * reserved on GET)
+ * output: args[0] = reserved[31:1] + current CC6 control[0]
+ * (last value configured via HSMP or APML)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /* RESERVED(0x2B-0x2F) */
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
*/
{0, 2, HSMP_GET},
+ /*
+ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1
+ * input: args[0] = reg space[23] + reg offset[22:12] +
+ * device LID[11:8] + DIMM address[7:0]
+ * output: args[0] = read data byte[3:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1
+ * input: args[0] = apic id of core[15:0]
+ * output: args[0] = CCD power(mWatts)[31:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1
+ * output: args[0] = thermal behaviour[31:0]
+ */
+ {0, 1, HSMP_GET},
+
+ /*
+ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1
+ * input: args[0] = SVI3 rail index[3:1] + read temperature[0]
+ * output: args[0] = SVI3 rail index[30:28] +
+ * rail temperature in degree C[27:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3
+ * input: args[0] = HSMP command mask[0]
+ * output: status of HSMP command = args[0], args[1], args[2]
+ */
+ {1, 3, HSMP_GET},
+
+ /*
+ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1
+ * input: args[0] = op[31:30] + reserved[29:28] +
+ * apic id[27:16] + floor frequency MHz[15:0]
+ * op encoding: 00 = set per-core floor,
+ * 01 = set all-cores floor (apic id reserved),
+ * 10 = get per-core floor,
+ * 11 = get per-core effective floor.
+ * Floor frequency field is reserved on GET (bit[31] = 1).
+ * output: args[0] = floor frequency MHz[15:0]
+ * (effective for op 11, configured for op 10;
+ * reserved on SET)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0
+ * input: args[0] = write data[31:24] + reg space[23] +
+ * reg offset[22:12] + device LID[11:8] +
+ * DIMM address[7:0]
+ */
+ {1, 0, HSMP_SET},
+
+ /*
+ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1
+ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) +
+ * SDPS limit[30:0]
+ * output: args[0] = SDPS limit[30:0]
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1
+ * input: args[0] = op[31:30] + priority sel[27:26] +
+ * priority val[21:20] + input[19:0]
+ * output: args[0] = supported priorities or priority val[1:0]
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2
+ * input: args[0] = op[31] + sub-op[30:29] + params[28:0]
+ * output: args[0] = discovery bits or floating/global memory BW (Gbps)
+ * output: args[1] = reserved or config (drop adj, sampling delay,
+ * hysteresis)
+ */
+ {1, 2, HSMP_SET_GET},
};
/* Metrics table (supported only with proto version 6) */
@@ -467,6 +612,39 @@ struct hsmp_metric_table {
__u32 gfxclk_frequency[8];
};
+/**
+ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL
+ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout
+ * stable between 32-bit and 64-bit callers) to the destination
+ * buffer that receives the metric table.
+ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the
+ * number of bytes copied out on success. Must be non-zero and no
+ * larger than the metric table size firmware reports for this
+ * socket; a larger value is rejected with -EINVAL rather than
+ * short-written. A smaller value returns the leading @size bytes
+ * of the snapshot. The kernel does not write this field back.
+ * @sock_ind: Input. Socket index from which the metric table is read.
+ * @reserved: Reserved for future use. Callers should set this to zero;
+ * future kernels may begin interpreting the field, so passing
+ * a non-zero value today is not forwards compatible.
+ *
+ * Placing @buf first lets all fields fall on their natural alignment under
+ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with
+ * the same wire layout on 32-bit and 64-bit userspace.
+ *
+ * The metric table layout depends on the HSMP protocol version reported by
+ * firmware, which userspace can read from the protocol_version sysfs
+ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on
+ * that version pass sizeof(struct hsmp_metric_table). Later version metrics
+ * table layout is documented in the Public PPR.
+ */
+struct hsmp_telemetry_data {
+ __u64 buf;
+ __u32 size;
+ __u16 sock_ind;
+ __u16 reserved;
+};
+
/* Reset to default packing */
#pragma pack()
@@ -474,4 +652,16 @@ struct hsmp_metric_table {
#define HSMP_BASE_IOCTL_NR 0xF8
#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message)
+/*
+ * Fetch the firmware metric (telemetry) table for a given socket via the
+ * HSMP character device. This avoids the PAGE_SIZE limitation of the
+ * sysfs binary attribute path for tables larger than one page (such as the
+ * ~13 KB table used by HSMP protocol version 7).
+ *
+ * The direction is _IOW because the kernel only reads the request struct;
+ * the table itself is written to the buffer that @buf points at.
+ */
+#define HSMP_IOCTL_GET_TELEMETRY_DATA \
+ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data)
+
#endif /*_ASM_X86_AMD_HSMP_H_*/
diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c
index e486109e88c8..75ed6ceb9df3 100644
--- a/arch/x86/platform/olpc/olpc-xo15-sci.c
+++ b/arch/x86/platform/olpc/olpc-xo15-sci.c
@@ -18,8 +18,6 @@
#define DRV_NAME "olpc-xo15-sci"
#define PFX DRV_NAME ": "
-#define XO15_SCI_CLASS DRV_NAME
-#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI"
static unsigned long xo15_sci_gpe;
static bool lid_wake_on_close;
@@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev)
if (!device)
return -ENODEV;
- strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
- strscpy(acpi_device_class(device), XO15_SCI_CLASS);
-
/* Get GPE bit assignment (EC events). */
status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp);
if (ACPI_FAILURE(status))
diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c
index 9309cfb935be..9a88d287fa09 100644
--- a/drivers/char/sonypi.c
+++ b/drivers/char/sonypi.c
@@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev)
return -ENODEV;
sonypi_acpi_device = device;
- strscpy(acpi_device_name(device), "Sony laptop hotkeys");
- strscpy(acpi_device_class(device), "sony/hotkey");
return 0;
}
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
index 097c5513ccd8..60c185a9cd50 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
@@ -7,6 +7,7 @@
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
+#include <linux/amd-pmf.h>
#include <linux/amd-pmf-io.h>
#include <linux/cleanup.h>
#include <linux/io-64-nonatomic-lo-hi.h>
@@ -137,20 +138,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
*platform_type = mode.op_mode.devicemode;
if (mode.op_mode.ontablestate == 1) {
- *laptop_placement = ON_TABLE;
+ *laptop_placement = AMD_PMF_ON_TABLE;
} else if (mode.op_mode.ontablestate == 2) {
- *laptop_placement = ON_LAP_MOTION;
+ *laptop_placement = AMD_PMF_ON_LAP_MOTION;
} else if (mode.op_mode.inbagstate == 1) {
- *laptop_placement = IN_BAG;
+ *laptop_placement = AMD_PMF_IN_BAG;
} else if (mode.op_mode.outbagstate == 1) {
- *laptop_placement = OUT_OF_BAG;
+ *laptop_placement = AMD_PMF_OUT_OF_BAG;
} else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 ||
mode.op_mode.outbagstate == 0) {
- *laptop_placement = LP_UNKNOWN;
+ *laptop_placement = AMD_PMF_LP_UNKNOWN;
pr_warn_once("Unknown laptop placement\n");
} else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 ||
mode.op_mode.outbagstate == 3) {
- *laptop_placement = LP_UNDEFINED;
+ *laptop_placement = AMD_PMF_LP_UNDEFINED;
pr_warn_once("Undefined laptop placement\n");
}
diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c
index 5ca7308c6077..4d2ad042a7f8 100644
--- a/drivers/platform/arm64/qcom-hamoa-ec.c
+++ b/drivers/platform/arm64/qcom-hamoa-ec.c
@@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp)
return ret;
else if (ret == 0 || ret == 0xff)
return -EOPNOTSUPP;
+ else if (ret != resp_len)
+ return -EIO;
- if (resp[0] >= resp_len)
+ if (resp[0] != resp_len - 1)
return -EINVAL;
return 0;
diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c
index f67c7f56ab2b..9ddc7fa1a973 100644
--- a/drivers/platform/mellanox/mlxbf-bootctl.c
+++ b/drivers/platform/mellanox/mlxbf-bootctl.c
@@ -10,6 +10,7 @@
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
+#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/if_ether.h>
#include <linux/iopoll.h>
diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c
index 5ec1ad471696..2ad9e2b0493c 100644
--- a/drivers/platform/mellanox/mlxbf-pmc.c
+++ b/drivers/platform/mellanox/mlxbf-pmc.c
@@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev)
static int mlxbf_pmc_probe(struct platform_device *pdev)
{
- struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev);
- const char *hid = acpi_device_hid(acpi_dev);
struct device *dev = &pdev->dev;
+ struct acpi_device *acpi_dev;
struct arm_smccc_res res;
+ const char *hid;
guid_t guid;
int ret;
+ acpi_dev = ACPI_COMPANION(&pdev->dev);
+ if (!acpi_dev)
+ return -ENODEV;
+
+ hid = acpi_device_hid(acpi_dev);
+
/* Ensure we have the UUID we expect for this service. */
arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res);
guid_parse(mlxbf_pmc_svc_uuid_str, &guid);
diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c
index a9dcb0bbe90e..593a7aba6243 100644
--- a/drivers/platform/surface/surface_acpi_notify.c
+++ b/drivers/platform/surface/surface_acpi_notify.c
@@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev)
static int san_probe(struct platform_device *pdev)
{
- struct acpi_device *san = ACPI_COMPANION(&pdev->dev);
struct ssam_controller *ctrl;
+ struct acpi_device *san;
struct san_data *data;
acpi_status astatus;
int status;
+ san = ACPI_COMPANION(&pdev->dev);
+ if (!san)
+ return -ENODEV;
+
ctrl = ssam_client_bind(&pdev->dev);
if (IS_ERR(ctrl))
return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c
index 8b8b80228c14..541e9180f976 100644
--- a/drivers/platform/surface/surface_aggregator_hub.c
+++ b/drivers/platform/surface/surface_aggregator_hub.c
@@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = {
/* -- Driver registration. -------------------------------------------------- */
static const struct ssam_device_id ssam_hub_match[] = {
- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub },
- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub },
+ {
+ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00),
+ .driver_data = (unsigned long)&kip_hub,
+ }, {
+ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00),
+ .driver_data = (unsigned long)&base_hub,
+ },
{ }
};
MODULE_DEVICE_TABLE(ssam, ssam_hub_match);
diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c
index ffa36ed92897..13031c329553 100644
--- a/drivers/platform/surface/surface_aggregator_tabletsw.c
+++ b/drivers/platform/surface/surface_aggregator_tabletsw.c
@@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = {
/* -- Driver registration. -------------------------------------------------- */
static const struct ssam_device_id ssam_tablet_sw_match[] = {
- { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc },
- { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc },
+ {
+ SSAM_SDEV(KIP, SAM, 0x00, 0x01),
+ .driver_data = (unsigned long)&ssam_kip_sw_desc,
+ }, {
+ SSAM_SDEV(POS, SAM, 0x00, 0x01),
+ .driver_data = (unsigned long)&ssam_pos_sw_desc,
+ },
{ },
};
MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match);
diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c
index 388a3e1a488c..b38aa010053e 100644
--- a/drivers/platform/surface/surfacepro3_button.c
+++ b/drivers/platform/surface/surfacepro3_button.c
@@ -20,7 +20,6 @@
#define SURFACE_PRO3_BUTTON_HID "MSHW0028"
#define SURFACE_PRO4_BUTTON_HID "MSHW0040"
#define SURFACE_BUTTON_OBJ_NAME "VGBI"
-#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons"
#define MSHW0040_DSM_REVISION 0x01
#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision
@@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev)
goto err_free_button;
}
- strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
snprintf(button->phys, sizeof(button->phys), "%s/buttons",
acpi_device_hid(device));
- input->name = acpi_device_name(device);
+ input->name = "Surface Pro 3/4 Buttons";
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &pdev->dev;
@@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev)
goto err_free_button;
}
- dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device),
- acpi_device_bid(device));
+ dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device));
+
return 0;
err_free_input:
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index b54b5212b204..957034f39e4e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -802,7 +802,6 @@ config LG_LAPTOP
tristate "LG Laptop Extras"
depends on ACPI
depends on ACPI_BATTERY
- depends on ACPI_WMI
depends on INPUT
select INPUT_SPARSEKMAP
select NEW_LEDS
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index e0eaaefb13d0..61ae622c93d9 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out)
return -EIO;
obj = result.pointer;
- if (obj && out) {
+ if (!obj && out) {
+ ret = -ENOMSG;
+ } else if (obj && out) {
switch (obj->type) {
case ACPI_TYPE_INTEGER:
*out = obj->integer.value;
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index b813f9265368..a1a74ef6c859 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -34,6 +34,17 @@ config AMD_WBRF
This mechanism will only be activated on platforms that advertise a
need for it.
+config AMD_HALO_LED
+ tristate "AMD Halo Box RGB LED Driver"
+ depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR
+ help
+ This driver provides RGB LED control for AMD Halo Box devices
+ through the LED multicolor subsystem. The Halo Box light bar can
+ be controlled via sysfs to display any RGB color combination.
+
+ To compile this driver as a module, choose M here: the module
+ will be called amd_halo_led.
+
config AMD_ISP_PLATFORM
tristate "AMD ISP4 platform driver"
depends on I2C && X86_64 && ACPI
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index f6ff0c837f34..2f467dbbfc8a 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/
obj-$(CONFIG_AMD_HSMP) += hsmp/
obj-$(CONFIG_AMD_PMF) += pmf/
obj-$(CONFIG_AMD_WBRF) += wbrf.o
+obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o
obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o
obj-$(CONFIG_AMD_HFI) += hfi/
diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c
new file mode 100644
index 000000000000..b21d956c7d96
--- /dev/null
+++ b/drivers/platform/x86/amd/amd_halo_led.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Halo Box RGB LED Driver
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * This driver provides RGB LED control for AMD Halo Box devices through
+ * the LED multicolor subsystem. The Halo Box light bar can be controlled
+ * via sysfs to display any RGB color combination.
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/container_of.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <linux/byteorder/generic.h>
+
+#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86"
+
+/* WMI method IDs from MOF */
+enum {
+ AMD_HALO_WMI_TURN_OFF = 0x04,
+ AMD_HALO_WMI_RGB = 0x07,
+};
+
+/* Arg0 of the AMD_HALO_WMI_RGB Method */
+enum {
+ AMD_HALO_RGB_CMD_GET = 0x0,
+ AMD_HALO_RGB_CMD_SET = 0x1,
+};
+
+/* Status codes from spec */
+#define AMD_HALO_STATUS_SUCCESS 0x0000
+#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD
+
+/* Brightness uses 0-100 range */
+#define AMD_HALO_MAX_HW_BRIGHTNESS 100
+
+/**
+ * struct amd_halo_led_data - Driver private data
+ * @wdev: WMI device pointer
+ * @led_mc: LED multicolor class device
+ * @subled_info: RGB channel information
+ * @lock: Mutex to protect WMI calls
+ */
+struct amd_halo_led_data {
+ struct wmi_device *wdev;
+ struct led_classdev_mc led_mc;
+ struct mc_subled subled_info[3];
+ struct mutex lock;
+};
+
+struct amd_halo_wmi_args {
+ __le32 arg0;
+ __le32 arg1;
+};
+
+struct amd_halo_wmi_args_rgb {
+ __le32 cmd;
+ __le32 red;
+ __le32 green;
+ __le32 blue;
+};
+
+struct amd_halo_wmi_output_rgb {
+ __le16 status;
+ u8 red;
+ u8 green;
+ u8 blue;
+} __packed;
+
+static inline int wmi_status_to_err(u16 status)
+{
+ switch (status) {
+ case AMD_HALO_STATUS_SUCCESS:
+ return 0;
+ case AMD_HALO_STATUS_INVALID_PARAM:
+ return -EINVAL;
+ default:
+ return -EIO;
+ }
+}
+
+static int __amd_halo_wmi_call(struct wmi_device *wdev,
+ u32 method_id, void *data, size_t length)
+{
+ struct wmi_buffer input = {
+ .length = length,
+ .data = data,
+ };
+ struct wmi_buffer output = { };
+ int ret;
+
+ /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */
+ ret = wmidev_invoke_method(wdev, 0, method_id,
+ &input, &output, sizeof(__le16));
+ if (ret)
+ return ret;
+
+ __le16 *result_status __free(kfree) = output.data;
+
+ return wmi_status_to_err(le16_to_cpu(*result_status));
+}
+
+/**
+ * amd_halo_wmi_turn_off - Turn off all LED channels
+ * @wdev: WMI device pointer
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int amd_halo_wmi_turn_off(struct wmi_device *wdev)
+{
+ struct amd_halo_wmi_args args = { };
+
+ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args));
+}
+
+/**
+ * amd_halo_wmi_set_rgb - Set all RGB channels atomically
+ * @wdev: WMI device pointer
+ * @r: brightness for red channel (0 - 100)
+ * @g: brightness for green channel (0 - 100)
+ * @b: brightness for blue channel (0 - 100)
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b)
+{
+ struct amd_halo_wmi_args_rgb args = {
+ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET),
+ .red = cpu_to_le32(r),
+ .green = cpu_to_le32(g),
+ .blue = cpu_to_le32(b),
+ };
+
+ if (r > AMD_HALO_MAX_HW_BRIGHTNESS ||
+ g > AMD_HALO_MAX_HW_BRIGHTNESS ||
+ b > AMD_HALO_MAX_HW_BRIGHTNESS) {
+ return -EINVAL;
+ }
+
+ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args));
+}
+
+/**
+ * amd_halo_wmi_get_rgb - Get RGB values
+ * @wdev: WMI device pointer
+ * @r: output buffer for red value
+ * @g: output buffer for green value
+ * @b: output buffer for blue value
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b)
+{
+ struct amd_halo_wmi_args_rgb args = {
+ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET),
+ };
+ struct wmi_buffer input = {
+ .length = sizeof(args),
+ .data = &args,
+ };
+ struct wmi_buffer output = { };
+ int ret;
+
+ ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB,
+ &input, &output, sizeof(struct amd_halo_wmi_output_rgb));
+ if (ret)
+ return ret;
+
+ struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data;
+
+ ret = wmi_status_to_err(le16_to_cpu(data->status));
+ if (ret)
+ return ret;
+
+ if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS ||
+ data->green > AMD_HALO_MAX_HW_BRIGHTNESS ||
+ data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) {
+ return -EPROTO;
+ }
+
+ *r = data->red;
+ *g = data->green;
+ *b = data->blue;
+
+ return 0;
+}
+
+/**
+ * amd_halo_brightness_set - Set LED brightness
+ * @cdev: LED class device
+ * @brightness: Brightness value
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int amd_halo_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct amd_halo_led_data *data = container_of(mc_cdev,
+ struct amd_halo_led_data,
+ led_mc);
+ u32 red_hw, green_hw, blue_hw;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ if (brightness == 0)
+ return amd_halo_wmi_turn_off(data->wdev);
+
+ red_hw = mc_cdev->subled_info[0].brightness;
+ green_hw = mc_cdev->subled_info[1].brightness;
+ blue_hw = mc_cdev->subled_info[2].brightness;
+
+ ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw);
+ if (ret)
+ goto out;
+
+ return 0;
+
+out:
+ /*
+ * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed.
+ * Attempt to turn the LED off completely as clean-up.
+ */
+ if (amd_halo_wmi_turn_off(data->wdev))
+ dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n");
+
+ return ret;
+}
+
+static int amd_halo_probe(struct wmi_device *wdev, const void *context)
+{
+ struct led_init_data led_init_data = {
+ .devicename = "amd_halo",
+ .default_label = "multicolor:" LED_FUNCTION_STATUS,
+ .devname_mandatory = true,
+ };
+ struct amd_halo_led_data *data;
+ u8 r, g, b;
+ int ret;
+
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&wdev->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ data->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, data);
+
+ data->subled_info[0].color_index = LED_COLOR_ID_RED;
+ data->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ data->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+
+ data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS;
+ data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS;
+ data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set;
+ data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ data->led_mc.num_colors = ARRAY_SIZE(data->subled_info);
+ data->led_mc.subled_info = data->subled_info;
+
+ ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b);
+ if (ret)
+ return ret;
+
+ data->subled_info[0].intensity = r;
+ data->subled_info[1].intensity = g;
+ data->subled_info[2].intensity = b;
+
+ ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc,
+ &led_init_data);
+ if (ret)
+ return dev_err_probe(&wdev->dev, ret,
+ "Failed to register multicolor LED\n");
+ return 0;
+}
+
+static const struct wmi_device_id amd_halo_id_table[] = {
+ { .guid_string = AMD_HALO_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, amd_halo_id_table);
+
+static struct wmi_driver amd_halo_driver = {
+ .driver = {
+ .name = "amd_halo_led",
+ },
+ .id_table = amd_halo_id_table,
+ .probe = amd_halo_probe,
+ .no_singleton = true,
+};
+
+module_wmi_driver(amd_halo_driver);
+
+MODULE_AUTHOR("Mario Limonciello (AMD) <superm1@kernel.org>");
+MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>");
+MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
index 97ed71593bdf..8257cd1da48e 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -15,12 +15,18 @@
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/ioport.h>
+#include <linux/kref.h>
#include <linux/kstrtox.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/topology.h>
#include <linux/uuid.h>
@@ -38,6 +44,17 @@
static struct hsmp_plat_device *hsmp_pdev;
+/*
+ * Tracks the ACPI socket platform devices that share the socket array and the
+ * /dev/hsmp misc device. The first probe initializes it, each further probe
+ * takes a reference and every remove (or probe failure) drops one; the last
+ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run
+ * under hsmp_sock_rwsem held for write, so the counting is already serialized
+ * and the atomic in kref is not strictly needed; kref is used for the clearer
+ * get/put interface and its release callback.
+ */
+static struct kref hsmp_acpi_sock_kref;
+
struct hsmp_sys_attr {
struct device_attribute dattr;
u32 msg_id;
@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
* bytes to integer.
*/
uid = acpi_device_uid(ACPI_COMPANION(dev));
+ if (!uid || strlen(uid) < 3)
+ return -EINVAL;
return kstrtou16(uid + 2, 10, sock_ind);
}
@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
return AE_OK;
}
-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
+static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *guid, *mailbox_package;
@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
int ret = 0;
int j;
- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
+ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
&buf, ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
+ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
acpi_format_exception(status));
return -ENODEV;
}
@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
guid = &dsd->package.elements[0];
mailbox_package = &dsd->package.elements[1];
if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
- dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
+ dev_err(dev, "Invalid hsmp _DSD table data\n");
ret = -EINVAL;
goto free_buf;
}
@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
union acpi_object *msgobj, *msgstr, *msgint;
msgobj = &mailbox_package->package.elements[j];
- msgstr = &msgobj->package.elements[0];
- msgint = &msgobj->package.elements[1];
/* package should have 1 string and 1 integer object */
if (msgobj->type != ACPI_TYPE_PACKAGE ||
- msgstr->type != ACPI_TYPE_STRING ||
+ msgobj->package.count < 2) {
+ ret = -EINVAL;
+ goto free_buf;
+ }
+
+ msgstr = &msgobj->package.elements[0];
+ msgint = &msgobj->package.elements[1];
+
+ if (msgstr->type != ACPI_TYPE_STRING ||
msgint->type != ACPI_TYPE_INTEGER) {
ret = -EINVAL;
goto free_buf;
@@ -183,14 +208,14 @@ free_buf:
return ret;
}
-static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
+static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock)
{
acpi_status status;
- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
+ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS,
hsmp_resource, sock);
if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
+ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
acpi_format_exception(status));
return -EINVAL;
}
@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
return -EINVAL;
/* The mapped region should be un-cached */
- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
+ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr,
sock->mbinfo.size);
if (!sock->virt_base_addr) {
- dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
+ dev_err(dev, "Failed to ioremap MP1 base address\n");
return -ENOMEM;
}
@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
int ret;
sock->sock_ind = sock_ind;
- sock->dev = dev;
sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr;
sema_init(&sock->hsmp_sem, 1);
@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
dev_set_drvdata(dev, sock);
/* Read MP1 base address from CRS method */
- ret = hsmp_read_acpi_crs(sock);
+ ret = hsmp_read_acpi_crs(dev, sock);
if (ret)
return ret;
/* Read mailbox offsets from DSD table */
- return hsmp_read_acpi_dsd(sock);
+ ret = hsmp_read_acpi_dsd(dev, sock);
+ if (ret)
+ return ret;
+
+ /*
+ * Publish sock->dev last. hsmp_send_message() uses it (via
+ * smp_load_acquire()) as the readiness gate for the lock-free data
+ * plane, so it must become visible only after virt_base_addr, the
+ * mailbox offsets and the semaphore are fully initialized. On a
+ * multi-socket system socket 0 exposes /dev/hsmp before later sockets
+ * finish probing, so without this an ioctl aimed at a socket still in
+ * bring-up could pass the gate and dereference a NULL virt_base_addr.
+ */
+ smp_store_release(&sock->dev, dev);
+
+ return 0;
}
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj
struct device *dev = container_of(kobj, struct device, kobj);
struct hsmp_socket *sock = dev_get_drvdata(dev);
+ /*
+ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE.
+ * It can therefore only carry the protocol version 6 metric table
+ * (struct hsmp_metric_table). The larger tables defined from protocol
+ * version 7 onwards do not fit; userspace on those systems must read
+ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp.
+ * Surface the unsupported case here as -EOPNOTSUPP rather than
+ * silently truncating the snapshot.
+ */
+ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6)
+ return -EOPNOTSUPP;
+
return hsmp_metric_tbl_read(sock, buf, count);
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
const struct bin_attribute *battr, int id)
{
- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
+ /*
+ * Keep metrics_bin visible on protocol version 7 and later as well,
+ * 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.
+ */
+ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
return battr->attr.mode;
return 0;
@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att
return len;
}
+/*
+ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox
+ * handshake and register its sysfs/hwmon interfaces.
+ *
+ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the
+ * per-socket bring-up cannot race a concurrent probe or remove.
+ */
static int init_acpi(struct device *dev)
{
u16 sock_ind;
int ret;
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
ret = hsmp_get_uid(dev, &sock_ind);
if (ret)
return ret;
@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev)
return ret;
}
- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
+ if (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");
@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
+/*
+ * kref release: tear down the shared ACPI socket state once the last socket
+ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any
+ * metric-table DRAM, destroy the per-socket mutexes and free the socket array.
+ *
+ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove
+ * and probe-failure paths both drop their reference under that lock. The write
+ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox
+ * and freeing the array cannot race the data plane.
+ */
+static void hsmp_acpi_sock_release(struct kref *kref)
+{
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
+ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device))
+ hsmp_misc_deregister();
+ hsmp_unmap_metric_tbls(hsmp_pdev);
+ hsmp_destroy_metric_read_locks(hsmp_pdev);
+ kfree(hsmp_pdev->sock);
+ hsmp_pdev->sock = NULL;
+ hsmp_pdev->num_sockets = 0;
+ hsmp_pdev->proto_ver = 0;
+}
+
+/**
+ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe.
+ * @dev: ACPI companion device whose probe failed.
+ *
+ * This device already took a reference on entry to hsmp_acpi_probe(), so clear
+ * its sock->dev and drop that reference; the shared state is released if it was
+ * the last one.
+ *
+ * Clearing sock->dev matters on multi-socket systems: when a non-first socket
+ * fails, the array stays alive (owned by an already-probed socket) and
+ * remove() is never called for this device, yet devres unmaps its mailbox once
+ * probe() returns. Without clearing dev, a later message to this index would
+ * pass every gate in hsmp_send_message() and reach the unmapped mailbox.
+ *
+ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata.
+ *
+ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write.
+ */
+static void hsmp_acpi_probe_failure_cleanup(struct device *dev)
+{
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
+ if (sock)
+ sock->dev = NULL;
+
+ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
+}
+
static int hsmp_acpi_probe(struct platform_device *pdev)
{
int ret;
@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev)
return -ENOMEM;
- if (!hsmp_pdev->is_probed) {
+ /*
+ * Multiple ACPI socket devices probe in parallel, but the one-time
+ * socket-array allocation and /dev/hsmp registration below must run
+ * exactly once. Hold the socket rwsem for write across the whole
+ * bring-up so it cannot race a concurrent probe or remove, and so the
+ * probe-failure teardown drains the data plane.
+ */
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
+ if (!hsmp_pdev->sock) {
hsmp_pdev->num_sockets = topology_max_packages();
if (!hsmp_pdev->num_sockets) {
dev_err(&pdev->dev, "No CPU sockets detected\n");
return -ENODEV;
}
- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
- sizeof(*hsmp_pdev->sock),
- GFP_KERNEL);
+ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets,
+ sizeof(*hsmp_pdev->sock),
+ GFP_KERNEL);
if (!hsmp_pdev->sock)
return -ENOMEM;
+
+ hsmp_init_metric_read_locks(hsmp_pdev);
+ kref_init(&hsmp_acpi_sock_kref);
+ } else {
+ kref_get(&hsmp_acpi_sock_kref);
}
+ /*
+ * This socket now holds a reference (kref_init on the first socket,
+ * kref_get afterwards). Every failure path below drops it via
+ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to
+ * hsmp_acpi_remove().
+ */
ret = init_acpi(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
+ hsmp_acpi_probe_failure_cleanup(&pdev->dev);
return ret;
}
- if (!hsmp_pdev->is_probed) {
- ret = hsmp_misc_register(&pdev->dev);
+ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) {
+ /*
+ * Register /dev/hsmp unparented. It is a singleton shared by all
+ * ACPI sockets and outlives all but the last of them, so
+ * parenting it to this socket's device would leave a dangling
+ * parent once that socket is unbound.
+ */
+ ret = hsmp_misc_register(NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to register misc device\n");
+ hsmp_acpi_probe_failure_cleanup(&pdev->dev);
return ret;
}
- hsmp_pdev->is_probed = true;
- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n");
+ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n");
}
return 0;
@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
static void hsmp_acpi_remove(struct platform_device *pdev)
{
+ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev);
+
/*
- * We register only one misc_device even on multi-socket system.
- * So, deregister should happen only once.
+ * Serialize the kref_put() and any release it triggers against a
+ * concurrent probe, and drain the data plane for the whole
+ * teardown: this covers the per-socket unbind, whose mailbox devres
+ * unmaps once we return, and the last unbind that frees the socket
+ * array in hsmp_acpi_sock_release().
*/
- if (hsmp_pdev->is_probed) {
- hsmp_misc_deregister();
- hsmp_pdev->is_probed = false;
- }
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
+ /*
+ * Clear this socket's dev so hsmp_send_message() rejects it before
+ * devres unmaps the mailbox. On a non-final unbind the socket array
+ * stays alive, so without this a later message to this index would
+ * reach an unmapped iomem region.
+ */
+ sock->dev = NULL;
+
+ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
}
static struct platform_driver amd_hsmp_driver = {
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index 6a26937fc2b5..5e123a4ecea9 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -10,10 +10,17 @@
#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/nospec.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
+#include <linux/slab.h>
#include <linux/sysfs.h>
+#include <linux/uaccess.h>
#include "hsmp.h"
@@ -41,6 +48,17 @@
static struct hsmp_plat_device hsmp_pdev;
/*
+ * Gates the AMD HSMP data plane against socket bring-up and teardown.
+ *
+ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads
+ * run concurrently. Probe and remove take it for write: probe brings sockets
+ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove
+ * tears them down, both excluding and draining the data plane.
+ */
+DECLARE_RWSEM(hsmp_sock_rwsem);
+EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP");
+
+/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
*
@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg)
return -EINVAL;
/*
- * Some older HSMP SET messages are updated to add GET in the same message.
- * In these messages, GET returns the current value and SET also returns
- * the successfully set value. To support this GET and SET in same message
- * while maintaining backward compatibility for the HSMP users,
- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz.
+ * 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.
*/
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) {
- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
- return -EINVAL;
- } else {
- /* only HSMP_SET or HSMP_GET messages go through this strict check */
- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
- return -EINVAL;
- }
+ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
+ return -EINVAL;
+
return 0;
}
-int hsmp_send_message(struct hsmp_message *msg)
+/*
+ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane
+ * takes it for read so many messages run concurrently, while the probe-time
+ * senders run under the write lock taken by probe. Holding it here serializes
+ * every message against socket teardown, which also holds it for write.
+ */
+static int hsmp_send_message_locked(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
unsigned int sock_ind;
int ret;
+ lockdep_assert_held(&hsmp_sock_rwsem);
+
if (!msg)
return -EINVAL;
ret = validate_message(msg);
@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg)
sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets);
sock = &hsmp_pdev.sock[sock_ind];
+ /*
+ * A slot exists for every possible socket, but it is only usable once
+ * that socket has actually been probed. Reject messages aimed at a
+ * socket that was never brought up or is still in bring-up, so we never
+ * operate on a zero-initialized semaphore or an unmapped mailbox. A
+ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and
+ * the semaphore are visible.
+ *
+ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev)
+ * in hsmp_parse_acpi_table().
+ */
+ if (!smp_load_acquire(&sock->dev))
+ return -ENODEV;
+
ret = down_interruptible(&sock->hsmp_sem);
if (ret < 0)
return ret;
@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg)
return ret;
}
+
+int hsmp_send_message(struct hsmp_message *msg)
+{
+ /*
+ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue
+ * messages from here. Take hsmp_sock_rwsem for read so messages run
+ * concurrently with each other but are drained and kept out while
+ * probe/remove hold it for write to tear a socket down.
+ */
+ guard(rwsem_read)(&hsmp_sock_rwsem);
+
+ return hsmp_send_message_locked(msg);
+}
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP");
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args)
@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value)
msg.args[0] = value;
msg.sock_ind = sock_ind;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg)
return false;
}
-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return 0;
}
-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
+static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
+ size_t size);
+
+/*
+ * Fetch the firmware metric (telemetry) table for the requested socket and
+ * copy it to the userspace buffer described by the request.
+ *
+ * The metric table size is variable across HSMP protocol versions and on
+ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer
+ * size, which may be anything up to the size firmware reported for this
+ * socket's table.
+ */
+static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg)
+{
+ void *kbuf __free(kvfree) = NULL;
+ void __user *arguser = (void __user *)arg;
+ struct hsmp_telemetry_data req;
+ struct hsmp_socket *sock;
+ void __user *user_buf;
+ size_t tbl_size;
+ unsigned int sock_ind;
+ int ret;
+
+ /* Telemetry data is read-only; require read access on the fd. */
+ if (!(fp->f_mode & FMODE_READ))
+ return -EPERM;
+
+ if (copy_from_user(&req, arguser, sizeof(req)))
+ return -EFAULT;
+
+ /*
+ * Reserved fields must be zero so future kernels can safely
+ * repurpose them without breaking already-deployed userspace.
+ */
+ if (req.reserved)
+ return -EINVAL;
+
+ user_buf = u64_to_user_ptr(req.buf);
+
+ /*
+ * /dev/hsmp is a singleton character device that outlives an individual
+ * socket unbind, so an ioctl on an already-open fd can run concurrently
+ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket
+ * lookup, the checks on its metric-table state and the read itself:
+ * probe and remove take the same lock for write, so they cannot free the
+ * socket array, unmap the table or destroy the per-socket mutex while
+ * this runs.
+ *
+ * The lock is dropped before the copy_to_user() below. Faulting in the
+ * destination can block indefinitely on a userfaultfd-backed buffer,
+ * which would leave a socket unbind waiting for the write lock.
+ */
+ scoped_guard(rwsem_read, &hsmp_sock_rwsem) {
+ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets)
+ return -ENODEV;
+
+ /*
+ * Sanitize the user-controlled socket index against speculative
+ * execution. The bounds check above retires the out-of-range
+ * case with -ENODEV, but a mispredicted branch can still let the
+ * CPU speculatively use sock_ind as an index into
+ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the
+ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns
+ * the bounds check into a data-flow clamp so the speculative
+ * load is in-range too.
+ */
+ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets);
+ sock = &hsmp_pdev.sock[sock_ind];
+ if (!sock->metric_tbl_addr)
+ return -ENODEV;
+
+ tbl_size = sock->metric_tbl_size;
+ if (!tbl_size)
+ return -ENODEV;
+
+ /*
+ * A request shorter than the firmware table is served with the
+ * leading @size bytes of the snapshot, so userspace built
+ * against an older table layout keeps working on firmware that
+ * grew the table. Asking for more than firmware provides is
+ * rejected rather than short-written, so a caller can never
+ * mistake a partial copy for a full one.
+ */
+ if (!req.size || req.size > tbl_size)
+ return -EINVAL;
+
+ /*
+ * The bounce buffer is overwritten in full by memcpy_fromio()
+ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid
+ * the zeroing cost of kvzalloc() on the ~13 KB allocation done
+ * on every ioctl call.
+ */
+ kbuf = kvmalloc(tbl_size, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(user_buf, kbuf, req.size))
+ return -EFAULT;
+
+ return 0;
+}
+
+long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case HSMP_IOCTL_CMD:
+ return hsmp_ioctl_msg(fp, arg);
+ case HSMP_IOCTL_GET_TELEMETRY_DATA:
+ return hsmp_ioctl_get_telemetry(fp, arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping
+ * and its metric_read_lock alive: probe and remove take the same lock for
+ * write while they bring sockets up and tear them down.
+ */
+static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
+ size_t size)
{
struct hsmp_message msg = { 0 };
int ret;
+ lockdep_assert_held(&hsmp_sock_rwsem);
+
if (!sock || !buf)
return -EINVAL;
@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
return -ENOMEM;
}
- /* Do not support lseek(), also don't allow more than the size of metric table */
- if (size != sizeof(struct hsmp_metric_table)) {
+ if (size != sock->metric_tbl_size) {
dev_err(sock->dev, "Wrong buffer size\n");
return -EINVAL;
}
@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
msg.msg_id = HSMP_GET_METRIC_TABLE;
msg.sock_ind = sock->sock_ind;
- ret = hsmp_send_message(&msg);
+ /*
+ * HSMP_GET_METRIC_TABLE 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.
+ */
+ guard(mutex)(&sock->metric_read_lock);
+
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
memcpy_fromio(buf, sock->metric_tbl_addr, size);
return size;
}
+
+ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
+{
+ guard(rwsem_read)(&hsmp_sock_rwsem);
+
+ return hsmp_metric_tbl_read_locked(sock, buf, size);
+}
EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP");
+void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev)
+{
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++)
+ mutex_init(&pdev->sock[i].metric_read_lock);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP");
+
+void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev)
+{
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++)
+ mutex_destroy(&pdev->sock[i].metric_read_lock);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP");
+
+void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev)
+{
+ struct hsmp_socket *sock;
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++) {
+ sock = &pdev->sock[i];
+ if (sock->metric_tbl_addr) {
+ iounmap(sock->metric_tbl_addr);
+ sock->metric_tbl_addr = NULL;
+ }
+ sock->metric_tbl_size = 0;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP");
+
int hsmp_get_tbl_dram_base(u16 sock_ind)
{
struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind];
struct hsmp_message msg = { 0 };
phys_addr_t dram_addr;
+ size_t tbl_size;
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;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind)
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
return -ENOMEM;
}
- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
- sizeof(struct hsmp_metric_table));
+ /*
+ * The ACPI socket array is shared across sockets and outlives a
+ * per-socket unbind, so metric_tbl_addr may hold a mapping from an
+ * earlier bind of this socket. Unmap it before remapping so an
+ * unbind/rebind cycle does not leak a metric-table mapping. This runs
+ * during probe before the metric sysfs attribute is exposed, so no
+ * reader can be using it.
+ */
+ if (sock->metric_tbl_addr) {
+ iounmap(sock->metric_tbl_addr);
+ sock->metric_tbl_addr = NULL;
+ }
+ sock->metric_tbl_size = 0;
+
+ /* SMU returns table size from Family 1Ah Model 50h and forward */
+ if (msg.args[2])
+ tbl_size = msg.args[2];
+ else
+ tbl_size = sizeof(struct hsmp_metric_table);
+
+ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size);
if (!sock->metric_tbl_addr) {
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
return -ENOMEM;
}
+ sock->metric_tbl_size = tbl_size;
+
return 0;
}
EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP");
@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind)
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (!ret)
hsmp_pdev.proto_ver = msg.args[0];
@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev)
hsmp_pdev.mdev.name = HSMP_CDEV_NAME;
hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR;
hsmp_pdev.mdev.fops = &hsmp_fops;
+ /*
+ * The caller chooses the parent. The platform driver has a single
+ * device whose lifetime matches /dev/hsmp and parents it there. The
+ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by
+ * per-socket devices that can be unbound individually and out of order,
+ * so parenting it to one would leave it attached to an already-removed
+ * device.
+ */
hsmp_pdev.mdev.parent = dev;
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
hsmp_pdev.mdev.mode = 0644;
@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP");
void hsmp_misc_deregister(void)
{
misc_deregister(&hsmp_pdev.mdev);
+ hsmp_pdev.mdev.this_device = NULL;
}
EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP");
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index 0509a442eaae..8dbff16a87b1 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -15,9 +15,12 @@
#include <linux/hwmon.h>
#include <linux/kconfig.h>
#include <linux/miscdevice.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
+#include <linux/types.h>
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
@@ -27,7 +30,7 @@
#define HSMP_DEVNODE_NAME "hsmp"
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
-#define DRIVER_VERSION "2.5"
+#define DRIVER_VERSION "2.6"
struct hsmp_mbaddr_info {
u32 base_addr;
@@ -41,8 +44,12 @@ struct hsmp_socket {
struct bin_attribute hsmp_attr;
struct hsmp_mbaddr_info mbinfo;
void __iomem *metric_tbl_addr;
+ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */
+ size_t metric_tbl_size;
void __iomem *virt_base_addr;
struct semaphore hsmp_sem;
+ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */
+ struct mutex metric_read_lock;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct device *dev;
u16 sock_ind;
@@ -54,7 +61,6 @@ struct hsmp_plat_device {
struct hsmp_socket *sock;
u32 proto_ver;
u16 num_sockets;
- bool is_probed;
};
int hsmp_cache_proto_ver(u16 sock_ind);
@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg);
void hsmp_misc_deregister(void);
int hsmp_misc_register(struct device *dev);
int hsmp_get_tbl_dram_base(u16 sock_ind);
+void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev);
+void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev);
+void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev);
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
struct hsmp_plat_device *get_hsmp_pdev(void);
#if IS_ENABLED(CONFIG_HWMON)
@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind);
static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; }
#endif
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args);
+
+/*
+ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and
+ * remove take it for write to bring sockets up and tear them down.
+ */
+extern struct rw_semaphore hsmp_sock_rwsem;
#endif /* HSMP_H */
diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c
index 0cc9a742497f..c8314eee06f4 100644
--- a/drivers/platform/x86/amd/hsmp/hwmon.c
+++ b/drivers/platform/x86/amd/hsmp/hwmon.c
@@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
if (attr != hwmon_power_cap)
return -EOPNOTSUPP;
+ if (val < 0)
+ return -EINVAL;
+
msg.num_args = 1;
msg.args[0] = val / MICROWATT_PER_MILLIWATT;
msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT;
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
index e07f68575055..e9b2b809c0f5 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -13,12 +13,14 @@
#include <linux/acpi.h>
#include <linux/build_bug.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
#include <linux/sysfs.h>
#include <asm/amd/node.h>
@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev)
return 0;
}
+/*
+ * The socket array is devm-managed and freed by the driver core, but the
+ * metric-table DRAM regions are mapped with plain ioremap() during probe and
+ * the per-socket mutexes need an explicit mutex_destroy(), neither of which
+ * devres covers.
+ *
+ * Take the data-plane rwsem for write to drain any in-flight
+ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop
+ * the global socket pointer, all before devres frees the array. Registered as
+ * a devres action so it runs on both remove and probe failure.
+ */
+static void hsmp_pltdrv_release(void *data)
+{
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+ hsmp_unmap_metric_tbls(hsmp_pdev);
+ hsmp_destroy_metric_read_locks(hsmp_pdev);
+ hsmp_pdev->sock = NULL;
+}
+
static int hsmp_pltdrv_probe(struct platform_device *pdev)
{
int ret;
@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
if (!hsmp_pdev->sock)
return -ENOMEM;
- ret = init_platform_device(&pdev->dev);
+ hsmp_init_metric_read_locks(hsmp_pdev);
+
+ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL);
+ if (ret)
+ return ret;
+
+ /*
+ * init_platform_device() runs the mailbox handshake via the probe-only
+ * senders, which issue messages through hsmp_send_message_locked() and
+ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's
+ * role as a socket bring-up path. The lock is not held across
+ * devm_add_action_or_reset() above so the release action, which also
+ * takes it for write, does not deadlock if that registration fails.
+ */
+ scoped_guard(rwsem_write, &hsmp_sock_rwsem)
+ ret = init_platform_device(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
return ret;
diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c
index 753d630f3283..8a33e75b71c3 100644
--- a/drivers/platform/x86/amd/pmc/mp1_stb.c
+++ b/drivers/platform/x86/amd/pmc/mp1_stb.c
@@ -157,7 +157,7 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 fsize, num_samples, val, stb_rdptr_offset = 0;
struct amd_stb_v2_data *stb_data_arr;
- int ret;
+ int ret = 0;
/* Write dummy postcode while reading the STB buffer */
ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC);
@@ -176,22 +176,24 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
* the enhanced dram size. Note that we land here only for the
* platforms that support enhanced dram size reporting.
*/
- if (dump_custom_stb)
- return amd_stb_handle_efr(filp);
+ if (dump_custom_stb) {
+ ret = amd_stb_handle_efr(filp);
+ goto out;
+ }
/* Get the num_samples to calculate the last push location */
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true);
- /* Clear msg_port for other SMU operation */
- dev->msg_port = MSG_PORT_PMC;
if (ret) {
dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
- return ret;
+ goto out;
}
fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize);
- if (!stb_data_arr)
- return -ENOMEM;
+ if (!stb_data_arr) {
+ ret = -ENOMEM;
+ goto out;
+ }
stb_data_arr->size = fsize;
@@ -214,7 +216,10 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
filp->private_data = stb_data_arr;
- return 0;
+out:
+ /* Restore the default message port for subsequent SMU operations */
+ dev->msg_port = MSG_PORT_PMC;
+ return ret;
}
static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
@@ -289,15 +294,12 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
u32 phys_addr_low, phys_addr_hi;
u64 stb_phys_addr;
u32 size = 0;
- int ret;
+ int ret = 0;
if (!enable_stb)
return 0;
- if (amd_is_stb_supported(dev)) {
- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
- &amd_stb_debugfs_fops_v2);
- } else {
+ if (!amd_is_stb_supported(dev)) {
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_stb_debugfs_fops);
return 0;
@@ -306,27 +308,52 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
/* Spill to DRAM feature uses separate SMU message port */
dev->msg_port = MSG_PORT_S2D;
- amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
- if (size != S2D_TELEMETRY_BYTES_MAX)
- return -EIO;
+ ret = amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
+ if (ret)
+ goto out;
+ if (size != S2D_TELEMETRY_BYTES_MAX) {
+ ret = -EIO;
+ goto out;
+ }
- /* Get DRAM size */
- ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true);
- if (ret || !dev->dram_size)
+ /* Get DRAM size; fall back to the default if the query fails */
+ if (amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true) ||
+ !dev->dram_size)
dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
/* Get STB DRAM address */
- amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true);
- amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true);
+ ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low,
+ dev->stb_arg.s2d_msg_id, true);
+ if (ret)
+ goto out;
+ ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi,
+ dev->stb_arg.s2d_msg_id, true);
+ if (ret)
+ goto out;
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
-
- /* Clear msg_port for other SMU operation */
- dev->msg_port = MSG_PORT_PMC;
+ if (!stb_phys_addr) {
+ dev_err(dev->dev, "S2D phys addr query returned invalid address\n");
+ ret = -ENXIO;
+ goto out;
+ }
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
- if (!dev->stb_virt_addr)
- return -ENOMEM;
+ if (!dev->stb_virt_addr) {
+ ret = -ENOMEM;
+ goto out;
+ }
- return 0;
+ /*
+ * Only expose stb_read once the buffer is mapped; otherwise a read
+ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no
+ * longer aborts probe.
+ */
+ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
+ &amd_stb_debugfs_fops_v2);
+
+out:
+ /* Restore the default message port for subsequent SMU operations */
+ dev->msg_port = MSG_PORT_PMC;
+ return ret;
}
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index 74ddf1d8289a..09a30f333759 100644
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -72,6 +72,14 @@ static const struct dmi_system_id fwbug_list[] = {
}
},
{
+ .ident = "T14 Gen2 AMD",
+ .driver_data = &quirk_s2idle_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"),
+ }
+ },
+ {
.ident = "T14 Gen1 AMD",
.driver_data = &quirk_s2idle_spurious_8042,
.matches = {
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index a37083cdb908..6792aa2c6187 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -939,9 +939,16 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
amd_pmc_dbgfs_register(dev);
+
+ /*
+ * STB is an optional debug facility (enable_stb); a failure to set it
+ * up must not stop the rest of the driver - most importantly the s0i3
+ * LPS0 handler - from working, so treat it as non-fatal.
+ */
err = amd_stb_s2d_init(dev);
if (err)
- goto err_pci_dev_put;
+ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n",
+ err);
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
amd_mp2_stb_init(dev);
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
index 25b8f7ae3abd..ad4faf18de47 100644
--- a/drivers/platform/x86/amd/pmf/Kconfig
+++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -30,3 +30,13 @@ config AMD_PMF_DEBUG
in the PMF config store.
Say Y here to enable more debug logs and Say N here if you are not sure.
+
+config AMD_PMF_UTIL_SUPPORT
+ bool "AMD PMF Util layer support"
+ depends on AMD_PMF
+ help
+ Enabling this option provides a character device for userspace to capture
+ PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic
+ Power Slider AC/DC) along with PMF metrics from the AMD PMF driver.
+
+ Say Y here to enable it and Say N here if you are not sure.
diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
index 5978464e0eb7..8cac51182433 100644
--- a/drivers/platform/x86/amd/pmf/Makefile
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -7,4 +7,6 @@
obj-$(CONFIG_AMD_PMF) += amd-pmf.o
amd-pmf-y := core.o acpi.o sps.o \
auto-mode.o cnqf.o \
- tee-if.o spc.o
+ tee-if.o spc.o metrics.o
+# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled
+amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index b9e5a2cf3aae..b4eae65e675b 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -11,13 +11,13 @@
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
+#include <linux/dev_printk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
-#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
@@ -26,6 +26,13 @@
#define AMD_PMF_REGISTER_RESPONSE 0xA78
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
+/* PMF-SMU communication registers for 1AH_M80H */
+#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04
+#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08
+#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C
+#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC
+#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0
+
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMF_MAPPING_SIZE 0x01000
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
@@ -48,7 +55,7 @@
#define DELAY_MAX_US 3000
/* override Metrics Table sample size time (in ms) */
-static int metrics_table_loop_ms = 1000;
+int metrics_table_loop_ms = 1000;
module_param(metrics_table_loop_ms, int, 0644);
MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)");
@@ -61,7 +68,15 @@ static bool smart_pc_support = true;
module_param(smart_pc_support, bool, 0444);
MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
-static struct device *pmf_device;
+static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ return true;
+ default:
+ return false;
+ }
+}
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void)
return POWER_SOURCE_DC;
}
-static void amd_pmf_get_metrics(struct work_struct *work)
-{
- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
- ktime_t time_elapsed_ms;
- int socket_power;
-
- guard(mutex)(&dev->update_mutex);
-
- /* Transfer table contents */
- memset(dev->buf, 0, sizeof(dev->m_table));
- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
-
- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
- /* Calculate the avg SoC power consumption */
- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
-
- if (dev->amt_enabled) {
- /* Apply the Auto Mode transition */
- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
- }
-
- if (dev->cnqf_enabled) {
- /* Apply the CnQF transition */
- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
- }
-
- dev->start_time = ktime_to_ms(ktime_get());
- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
-}
-
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
{
return ioread32(dev->regbase + reg_offset);
@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
{
u32 value;
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
+ if (amd_pmf_supports_accumulator_metrics(dev)) {
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value);
+ }
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
guard(mutex)(&dev->lock);
/* Wait until we get a valid response */
- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
}
/* Write zero to response register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
+ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0);
/* Write argument into argument register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
+ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg);
/* Write message ID to message ID register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
+ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message);
/* Wait until we get a valid response */
- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
if (get) {
/* PMFW may take longer time to return back the data */
usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
+ if (amd_pmf_supports_accumulator_metrics(dev) &&
+ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) {
+ dev->dram_addr.hi = amd_pmf_reg_read(dev,
+ dev->smu_regs->arg_reg[1]);
+ dev->dram_addr.size = amd_pmf_reg_read(dev,
+ dev->smu_regs->arg_reg[2]);
+ }
}
break;
case AMD_PMF_RESULT_CMD_REJECT_BUSY:
@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
return rc;
}
-static const struct pci_device_id pmf_pci_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
- { }
+/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */
+static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = {
+ .msg_reg = AMD_PMF_REGISTER_MESSAGE,
+ .resp_reg = AMD_PMF_REGISTER_RESPONSE,
+ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 },
};
-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
-{
- u64 phys_addr;
- u32 hi, low;
-
- /* Get Metrics Table Address */
- if (alloc_buffer) {
- switch (dev->cpu_id) {
- case AMD_CPU_ID_PS:
- case AMD_CPU_ID_RMB:
- dev->mtable_size = sizeof(dev->m_table);
- break;
- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
- dev->mtable_size = sizeof(dev->m_table_v2);
- break;
- default:
- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
- }
-
- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
- if (!dev->buf)
- return -ENOMEM;
- }
-
- phys_addr = virt_to_phys(dev->buf);
- hi = phys_addr >> 32;
- low = phys_addr & GENMASK(31, 0);
-
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
-
- return 0;
-}
-
-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
-{
- int ret;
-
- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
-
- ret = amd_pmf_set_dram_addr(dev, true);
- if (ret)
- return ret;
-
- /*
- * Start collecting the metrics data after a small delay
- * or else, we might end up getting stale values from PMFW.
- */
- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
-
- return 0;
-}
-
-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
-{
- switch (pdev->cpu_id) {
- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
- return 0;
- default:
- return -EOPNOTSUPP;
- }
-}
-
-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
-{
- int ret, i;
-
- guard(mutex)(&dev->metrics_mutex);
-
- ret = is_npu_metrics_supported(dev);
- if (ret)
- return ret;
-
- ret = amd_pmf_set_dram_addr(dev, true);
- if (ret)
- return ret;
-
- memset(dev->buf, 0, dev->mtable_size);
-
- /* Send SMU command to get NPU metrics */
- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
- if (ret) {
- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
- return ret;
- }
-
- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
-
- data->npuclk_freq = dev->m_table_v2.npuclk_freq;
- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
- data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
- data->npu_power = dev->m_table_v2.npu_power;
- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
- data->npu_reads = dev->m_table_v2.npu_reads;
- data->npu_writes = dev->m_table_v2.npu_writes;
-
- return 0;
-}
-
-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
-{
- struct amd_pmf_dev *pdev;
-
- if (!info)
- return -EINVAL;
-
- if (!pmf_device)
- return -ENODEV;
-
- pdev = dev_get_drvdata(pmf_device);
- if (!pdev)
- return -ENODEV;
+/* 1AH_M80H uses an extended mailbox with three argument registers */
+static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = {
+ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2,
+ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2,
+ .arg_reg = {
+ AMD_PMF_REGISTER_ARGUMENT0_V2,
+ AMD_PMF_REGISTER_ARGUMENT1_V2,
+ AMD_PMF_REGISTER_ARGUMENT2_V2,
+ },
+};
- return amd_pmf_get_smu_metrics(pdev, info);
-}
-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
+static const struct pci_device_id pmf_pci_ids[] = {
+ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) },
+ { }
+};
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
{
@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
}
}
+static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev)
+{
+ const struct pci_device_id *id;
+
+ id = pci_match_id(pmf_pci_ids, rdev);
+ if (!id)
+ return -ENODEV;
+
+ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data;
+
+ return 0;
+}
+
static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0100", 0x100},
{"AMDI0102", 0},
@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0105", 0},
{"AMDI0107", 0},
{"AMDI0108", 0},
+ {"AMDI0109", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (err)
return err;
+ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */
+ err = amd_pmf_get_smu_mb_offset(dev, rdev);
+ if (err)
+ return err;
+
+ if (amd_pmf_supports_accumulator_metrics(dev)) {
+ err = amd_pmf_get_tbl_dram_addr(dev);
+ if (err)
+ return err;
+ }
+
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
- pmf_device = dev->dev;
+ amd_pmf_set_device(dev->dev);
+
+ err = amd_pmf_cdev_register(dev);
+ if (err)
+ dev_warn(dev->dev, "failed to register util interface: %d\n", err);
dev_info(dev->dev, "registered PMF device successfully\n");
@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev)
{
struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
+ amd_pmf_cdev_unregister();
amd_pmf_deinit_features(dev);
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD);
diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c
new file mode 100644
index 000000000000..5c0e368ec8df
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/metrics.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Driver - Metrics Support
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Patil Rajesh Reddy <Patil.Reddy@amd.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/io.h>
+#include <linux/ktime.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/wordpart.h>
+#include <linux/workqueue.h>
+
+#include "pmf.h"
+
+static struct device *pmf_device;
+
+static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc)
+{
+ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT;
+
+ return min_t(u16, val, U16_MAX);
+}
+
+static u16 amd_pmf_q10_acc_to_int(u64 avg_acc)
+{
+ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024);
+
+ return min_t(u16, val, U16_MAX);
+}
+
+static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter,
+ u64 curr_value, u64 prev_value)
+{
+ u32 counter_diff;
+ u64 val_diff;
+
+ /* Check for counter reset */
+ if (curr_counter <= prev_counter)
+ return 0;
+
+ counter_diff = curr_counter - prev_counter;
+
+ if (curr_value < prev_value)
+ return 0;
+
+ val_diff = curr_value - prev_value;
+
+ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff);
+}
+
+int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD,
+ ARG_NONE, &dev->dram_addr.lo);
+ if (ret) {
+ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret);
+ return ret;
+ }
+
+ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo;
+ dev->mtable_size = dev->dram_addr.size;
+
+ if (dev->mtable_size != sizeof(dev->mtable_v3)) {
+ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n",
+ dev->dram_addr.size, sizeof(dev->mtable_v3));
+ return -EINVAL;
+ }
+
+ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size);
+ if (!dev->metrics_table_virt) {
+ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n");
+ dev->metrics_table_phys = 0;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ if (!dev->metrics_table_virt) {
+ dev_err(dev->dev, "Metrics DRAM not mapped\n");
+ return -EINVAL;
+ }
+
+ /* Send command to PMFW to update metrics table log sample */
+ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL);
+ if (ret) {
+ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void amd_pmf_get_metrics(struct work_struct *work)
+{
+ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
+ ktime_t time_elapsed_ms;
+ int socket_power;
+
+ guard(mutex)(&dev->update_mutex);
+
+ /* Transfer table contents */
+ memset(dev->buf, 0, sizeof(dev->m_table));
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
+
+ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
+ /* Calculate the avg SoC power consumption */
+ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
+
+ if (dev->amt_enabled) {
+ /* Apply the Auto Mode transition */
+ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
+ }
+
+ if (dev->cnqf_enabled) {
+ /* Apply the CnQF transition */
+ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
+ }
+
+ dev->start_time = ktime_to_ms(ktime_get());
+ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
+}
+
+int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
+{
+ u64 phys_addr;
+ u32 hi, low;
+
+ /* Get Metrics Table Address */
+ if (alloc_buffer) {
+ switch (dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ case AMD_CPU_ID_RMB:
+ dev->mtable_size = sizeof(dev->m_table);
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ dev->mtable_size = sizeof(dev->m_table_v2);
+ break;
+ default:
+ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id);
+ }
+
+ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
+ if (!dev->buf)
+ return -ENOMEM;
+ }
+
+ phys_addr = virt_to_phys(dev->buf);
+ hi = upper_32_bits(phys_addr);
+ low = lower_32_bits(phys_addr);
+
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
+
+ return 0;
+}
+
+int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
+
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ /*
+ * Start collecting the metrics data after a small delay
+ * or else, we might end up getting stale values from PMFW.
+ */
+ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
+
+ return 0;
+}
+
+void amd_pmf_set_device(struct device *p_device)
+{
+ pmf_device = p_device;
+}
+
+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev,
+ struct amd_pmf_npu_metrics *data)
+{
+ struct amd_pmf_metrics_iod *curr, *prev;
+ u64 val;
+ int i;
+
+ curr = &dev->mtable_v3.iod;
+ prev = &dev->prev_metrics.iod;
+
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_temp_acc, prev->npu_temp_acc);
+ data->npu_temp = amd_pmf_q10_acc_to_int(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_power_acc, prev->npu_power_acc);
+ data->npu_power = amd_pmf_q10_acc_to_mW(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npuhclk_freq_eff_acc,
+ prev->npuhclk_freq_eff_acc);
+ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->aieclk_freq_eff_acc,
+ prev->aieclk_freq_eff_acc);
+ data->npuclk_freq = amd_pmf_q10_acc_to_int(val);
+
+ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) {
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_busy_acc[i],
+ prev->npu_busy_acc[i]);
+ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val);
+ }
+}
+
+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
+{
+ int ret, i;
+
+ guard(mutex)(&dev->metrics_mutex);
+
+ ret = is_npu_metrics_supported(dev);
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(*data));
+
+ switch (dev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ memset(dev->buf, 0, dev->mtable_size);
+
+ /* Send SMU command to get NPU metrics */
+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ if (ret) {
+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+
+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
+ data->npu_power = dev->m_table_v2.npu_power;
+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
+ data->npu_reads = dev->m_table_v2.npu_reads;
+ data->npu_writes = dev->m_table_v2.npu_writes;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ ret = amd_pmf_get_metrics_table_log_sample(dev);
+ if (ret)
+ return ret;
+
+ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3));
+
+ /*
+ * Ignore the first sample, as previous metrics is uninitialized (zero)
+ * Metrics are calculated as:
+ * metrics = current_metrics - previous_metrics
+ * Skipping the initial sample ensures accurate delta calculations.
+ */
+ if (!dev->npu_metrics_have_prev) {
+ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
+ dev->npu_metrics_have_prev = true;
+ return 0;
+ }
+
+ amd_pmf_calculate_acc_npu_metrics(dev, data);
+ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
+ break;
+ }
+
+ return 0;
+}
+
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
+{
+ struct amd_pmf_dev *pdev;
+
+ if (!info)
+ return -EINVAL;
+
+ if (!pmf_device)
+ return -ENODEV;
+
+ pdev = dev_get_drvdata(pmf_device);
+ if (!pdev)
+ return -ENODEV;
+
+ return amd_pmf_get_smu_metrics(pdev, info);
+}
+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 69fef7448744..07ec00684233 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -14,10 +14,13 @@
#include <linux/acpi.h>
#include <linux/amd-pmf-io.h>
#include <linux/circ_buf.h>
+#include <linux/compiler_attributes.h>
+#include <linux/compiler_types.h>
#include <linux/input.h>
#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
+#include <linux/types.h>
#define POLICY_BUF_MAX_SZ 0x4b000
#define POLICY_SIGN_COOKIE 0x31535024
@@ -28,6 +31,11 @@
#define AMD_CPU_ID_PS 0x14e8
#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
+
+/* Aliases required by PCI_DEVICE_DATA() macro naming convention */
+#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB
+#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS
struct cookie_header {
u32 sign;
@@ -70,6 +78,10 @@ struct cookie_header {
#define SET_PMF_PPT 0x25
#define SET_PMF_PPT_APU_ONLY 0x26
+/* Message IDs for 1AH_M80H platform */
+#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E
+#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F
+
/* OS slider update notification */
#define DC_BEST_PERF 0
#define DC_BETTER_PERF 1
@@ -130,6 +142,14 @@ struct cookie_header {
#define GET_CMD true
#define METRICS_TABLE_ID 7
+#define BIOS_OUTPUT_MAX 10
+
+extern int metrics_table_loop_ms;
+
+#define AMD_PMF_METRIC_CORE_TYPE_MAX 4
+#define AMD_PMF_METRIC_CCX_MAX 4
+#define AMD_PMF_NUM_CLK_DPM_LEVELS 8
+#define AMD_PMF_NUM_MAX_CORES 12
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
@@ -242,6 +262,199 @@ struct apmf_fan_idx {
u32 fan_ctl_idx;
} __packed;
+struct amd_pmf_metrics_iod {
+ u32 counter_acc;
+ /* Set Voltage */
+ 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 - STAPM */
+ 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[3];
+ /* STT Limits */
+ u32 stt_min_limit;
+ u64 stt_apu_hotspot_temp_acc;
+ u64 stt_hs2_hotspot_temp_acc;
+ u32 stt_apu_temp_limit;
+ u64 stt_apu_skin_temp_acc;
+ /* Residencies */
+ u64 cpuoff_residency_ccx0;
+ u64 cpuoff_residency_ccx1;
+ u64 cpuoff_residency_ccx2;
+ u64 cpuoff_residency_ccx3;
+ /* DF-pstates */
+ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ /* System */
+ u8 gfx_disabled;
+ u8 spare2[3];
+ u32 gfxclk_fmax;
+ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ /* 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];
+ /* Voltage Guardband in PSM count */
+ s8 psm_guardband[5][5][3];
+ s32 core_power_limit_offset;
+ u32 max_freq_offset[5];
+ u64 npu_temp_acc;
+ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ 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];
+} __packed __aligned(4);
+
+struct amd_pmf_metrics_ccx {
+ u64 core_c0[AMD_PMF_NUM_MAX_CORES];
+ u64 core_cc6[AMD_PMF_NUM_MAX_CORES];
+ u64 core_freq[AMD_PMF_NUM_MAX_CORES];
+ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES];
+ u64 core_temp[AMD_PMF_NUM_MAX_CORES];
+ u64 core_power[AMD_PMF_NUM_MAX_CORES];
+} __packed __aligned(4);
+
+struct amd_pmf_metrics_v3 {
+ struct amd_pmf_metrics_iod iod;
+ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX];
+} __packed __aligned(4);
+
struct smu_pmf_metrics_v2 {
u16 core_frequency[16]; /* MHz */
u16 core_power[16]; /* mW */
@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer {
int tail;
};
+/* SoC-specific SMU mailbox register offsets */
+struct amd_pmf_smu_regs {
+ u32 msg_reg;
+ u32 resp_reg;
+ u32 arg_reg[3];
+};
+
+struct amd_pmf_arg_data {
+ u32 lo;
+ u32 hi;
+ u32 size;
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -442,6 +668,14 @@ struct amd_pmf_dev {
struct pmf_cbi_ring_buffer cbi_buf;
struct mutex cbi_mutex; /* Protects ring buffer access */
struct mutex metrics_mutex;
+ u32 bios_output[BIOS_OUTPUT_MAX];
+ const struct amd_pmf_smu_regs *smu_regs;
+ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */
+ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */
+ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */
+ struct amd_pmf_arg_data dram_addr;
+ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */
+ bool npu_metrics_have_prev;
};
struct apmf_sps_prop_granular_v2 {
@@ -680,14 +914,6 @@ enum system_state {
SYSTEM_STATE_MAX,
};
-enum ta_slider {
- TA_BEST_BATTERY,
- TA_BETTER_BATTERY,
- TA_BETTER_PERFORMANCE,
- TA_BEST_PERFORMANCE,
- TA_MAX,
-};
-
struct amd_pmf_pb_bitmap {
const char *name;
u32 bit_mask;
@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
{"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
};
-enum platform_type {
- PTYPE_UNKNOWN = 0,
- LID_CLOSE,
- CLAMSHELL,
- FLAT,
- TENT,
- STAND,
- TABLET,
- BOOK,
- PRESENTATION,
- PULL_FWD,
- PTYPE_INVALID = 0xf,
-};
-
/* Command ids for TA communication */
enum ta_pmf_command {
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
@@ -871,6 +1083,10 @@ 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);
+
+/* Metrics layer */
+int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
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);
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
+/* Util Layer */
+#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT)
+int amd_pmf_cdev_register(struct amd_pmf_dev *dev);
+void amd_pmf_cdev_unregister(void);
+#else
+static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; }
+static inline void amd_pmf_cdev_unregister(void) {}
+#endif
+
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index f48678a23cc7..94355b435a66 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -10,6 +10,7 @@
*/
#include <acpi/button.h>
+#include <linux/amd-pmf.h>
#include <linux/amd-pmf-io.h>
#include <linux/cleanup.h>
#include <linux/power_supply.h>
@@ -17,61 +18,7 @@
#include "pmf.h"
#ifdef CONFIG_AMD_PMF_DEBUG
-static const char *platform_type_as_str(u16 platform_type)
-{
- switch (platform_type) {
- case CLAMSHELL:
- return "CLAMSHELL";
- case FLAT:
- return "FLAT";
- case TENT:
- return "TENT";
- case STAND:
- return "STAND";
- case TABLET:
- return "TABLET";
- case BOOK:
- return "BOOK";
- case PRESENTATION:
- return "PRESENTATION";
- case PULL_FWD:
- return "PULL_FWD";
- default:
- return "UNKNOWN";
- }
-}
-
-static const char *laptop_placement_as_str(u16 device_state)
-{
- switch (device_state) {
- case ON_TABLE:
- return "ON_TABLE";
- case ON_LAP_MOTION:
- return "ON_LAP_MOTION";
- case IN_BAG:
- return "IN_BAG";
- case OUT_OF_BAG:
- return "OUT_OF_BAG";
- default:
- return "UNKNOWN";
- }
-}
-
-static const char *ta_slider_as_str(unsigned int state)
-{
- switch (state) {
- case TA_BEST_PERFORMANCE:
- return "PERFORMANCE";
- case TA_BETTER_PERFORMANCE:
- return "BALANCED";
- case TA_BEST_BATTERY:
- return "POWER_SAVER";
- default:
- return "Unknown TA Slider State";
- }
-}
-
-static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
+u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
{
switch (index) {
case 0 ... 1:
@@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int
return 0;
}
}
+EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
int i;
dev_dbg(dev->dev, "==== TA inputs START ====\n");
- dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
+ dev_dbg(dev->dev, "Slider State: %s\n",
+ amd_pmf_get_slider_position(in->ev_info.power_slider));
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage);
dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design);
@@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open");
dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away");
dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light);
- dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
+ dev_dbg(dev->dev, "Platform type: %s\n",
+ amd_pmf_get_platform_type(in->ev_info.platform_type));
dev_dbg(dev->dev, "Laptop placement: %s\n",
- laptop_placement_as_str(in->ev_info.device_state));
+ amd_pmf_get_laptop_placement(in->ev_info.device_state));
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
amd_pmf_get_ta_custom_bios_inputs(in, i));
@@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_
switch (dev->current_profile) {
case PLATFORM_PROFILE_PERFORMANCE:
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
- val = TA_BEST_PERFORMANCE;
+ val = AMD_PMF_TA_BEST_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
- val = TA_BETTER_PERFORMANCE;
+ val = AMD_PMF_TA_BETTER_PERFORMANCE;
break;
case PLATFORM_PROFILE_LOW_POWER:
case PLATFORM_PROFILE_QUIET:
- val = TA_BEST_BATTERY;
+ val = AMD_PMF_TA_BEST_BATTERY;
break;
default:
dev_err(dev->dev, "Unknown Platform Profile.\n");
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index 7ccd93f506b2..3fe79f0f37c5 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -8,7 +8,9 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
+#include <linux/array_size.h>
#include <linux/debugfs.h>
+#include <linux/dev_printk.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#include "pmf.h"
@@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx)
static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action)
{
- u32 bios_idx;
+ int bios_idx;
+ int ret;
bios_idx = amd_pmf_get_bios_output_idx(action->action_index);
+ if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) {
+ dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx);
+ return;
+ }
- amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
+ ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
+ if (!ret)
+ pdev->bios_output[bios_idx] = action->value;
}
static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out)
diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c
new file mode 100644
index 000000000000..4d1a61e4f1b9
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/util.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Util Layer
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Sanket Goswami <Sanket.Goswami@amd.com>
+ */
+
+#include <linux/amd-pmf.h>
+#include <linux/minmax.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#include "pmf.h"
+
+static struct amd_pmf_dev *pmf_dev_handle;
+static DEFINE_MUTEX(pmf_util_lock);
+
+static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info)
+{
+ struct ta_pmf_shared_memory *ta_sm = NULL;
+ struct ta_pmf_enact_table *in = NULL;
+ int idx;
+
+ if (!pdev || !info)
+ return -EINVAL;
+
+ if (!pdev->shbuf)
+ return -EINVAL;
+
+ ta_sm = pdev->shbuf;
+ in = &ta_sm->pmf_input.enact_table;
+
+ /* Set size */
+ info->size = sizeof(*info);
+
+ /* PMF Feature support flags */
+ if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE))
+ info->features_supported |= AMD_PMF_FEAT_AUTO_MODE;
+ if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER;
+ if (pdev->smart_pc_enabled)
+ info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER;
+ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC))
+ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC;
+ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC))
+ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC;
+
+ /* Device States */
+ info->platform_type = in->ev_info.platform_type;
+ info->laptop_placement = in->ev_info.device_state;
+ info->lid_state = in->ev_info.lid_state;
+ info->user_presence = in->ev_info.user_present;
+ info->slider_position = in->ev_info.power_slider;
+
+ /* Thermal and Power Metrics */
+ info->power_source = in->ev_info.power_source;
+ info->skin_temp = in->ev_info.skin_temperature;
+ info->gfx_busy = in->ev_info.gfx_busy;
+ info->ambient_light = in->ev_info.ambient_light;
+ info->avg_c0_residency = in->ev_info.avg_c0residency;
+ info->max_c0_residency = in->ev_info.max_c0residency;
+ info->socket_power = in->ev_info.socket_power;
+
+ /* Custom BIOS input parameters */
+ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++)
+ info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx);
+
+ /* BIOS output parameters */
+ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++)
+ info->bios_output[idx] = pdev->bios_output[idx];
+
+ return 0;
+}
+
+static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct amd_pmf_dev *pdev = filp->private_data;
+ void __user *argp = (void __user *)arg;
+ struct amd_pmf_info info = {};
+ size_t copy_size;
+ __u64 user_size;
+ int ret;
+
+ if (cmd != IOCTL_AMD_PMF_POPULATE_DATA)
+ return -ENOTTY;
+
+ /* First read just the size field from userspace */
+ if (copy_from_user(&user_size, argp, sizeof(user_size)))
+ return -EFAULT;
+
+ guard(mutex)(&pmf_util_lock);
+ ret = amd_pmf_populate_data(pdev, &info);
+ if (ret)
+ return ret;
+
+ copy_size = min_t(size_t, user_size, sizeof(info));
+
+ /* Set actual size being copied */
+ info.size = copy_size;
+
+ if (copy_to_user(argp, &info, copy_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int amd_pmf_open(struct inode *inode, struct file *filp)
+{
+ guard(mutex)(&pmf_util_lock);
+ if (!pmf_dev_handle)
+ return -ENODEV;
+
+ filp->private_data = pmf_dev_handle;
+ return 0;
+}
+
+static const struct file_operations pmf_if_ops = {
+ .owner = THIS_MODULE,
+ .open = amd_pmf_open,
+ .unlocked_ioctl = amd_pmf_set_ioctl,
+};
+
+static struct miscdevice amd_pmf_util_if = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "amdpmf_interface",
+ .fops = &pmf_if_ops,
+};
+
+int amd_pmf_cdev_register(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ guard(mutex)(&pmf_util_lock);
+ pmf_dev_handle = dev;
+ ret = misc_register(&amd_pmf_util_if);
+ if (ret)
+ pmf_dev_handle = NULL;
+
+ return ret;
+}
+
+void amd_pmf_cdev_unregister(void)
+{
+ guard(mutex)(&pmf_util_lock);
+ if (pmf_dev_handle)
+ misc_deregister(&amd_pmf_util_if);
+ pmf_dev_handle = NULL;
+}
diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c
index 495dc1e31d40..93d9665717af 100644
--- a/drivers/platform/x86/asus-armoury.c
+++ b/drivers/platform/x86/asus-armoury.c
@@ -16,6 +16,7 @@
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/err.h>
@@ -93,6 +94,8 @@ struct asus_armoury_priv {
u32 mini_led_dev_id;
u32 gpu_mux_dev_id;
+
+ bool requires_fan_curve;
};
static struct asus_armoury_priv asus_armoury = {
@@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr,
u32 result;
int err;
+ /* On some models, PPT changes require an active fan curve */
+ if (asus_armoury.requires_fan_curve) {
+ switch (dev_id) {
+ case ASUS_WMI_DEVID_PPT_PL1_SPL:
+ case ASUS_WMI_DEVID_PPT_PL2_SPPT:
+ case ASUS_WMI_DEVID_PPT_PL3_FPPT:
+ case ASUS_WMI_DEVID_PPT_APU_SPPT:
+ case ASUS_WMI_DEVID_PPT_PLAT_SPPT:
+ if (!asus_wmi_custom_fan_curve_is_enabled()) {
+ pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n");
+ return -EBUSY;
+ }
+ break;
+ }
+ }
+
/*
* Prevent developers from bricking devices or issuing dangerous
* commands that can be difficult or impossible to recover from.
@@ -989,10 +1008,11 @@ fail_class_get:
/* Init / exit ****************************************************************/
/* Set up the min/max and defaults for ROG tunables */
-static void init_rog_tunables(void)
+static int init_rog_tunables(void)
{
const struct power_limits *ac_limits, *dc_limits;
- struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL;
+ struct rog_tunables *ac_rog_tunables __free(kfree) = NULL;
+ struct rog_tunables *dc_rog_tunables __free(kfree) = NULL;
const struct power_data *power_data;
const struct dmi_system_id *dmi_id;
@@ -1000,24 +1020,25 @@ static void init_rog_tunables(void)
dmi_id = dmi_first_match(power_limits);
if (!dmi_id) {
pr_warn("No matching power limits found for this system\n");
- return;
+ return 0;
}
/* Get the power data for this system */
power_data = dmi_id->driver_data;
if (!power_data) {
pr_info("No power data available for this system\n");
- return;
+ return 0;
}
+ asus_armoury.requires_fan_curve = power_data->requires_fan_curve;
+
/* Initialize AC power tunables */
ac_limits = power_data->ac_data;
if (ac_limits) {
- ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
+ ac_rog_tunables = kzalloc_obj(*ac_rog_tunables);
if (!ac_rog_tunables)
- goto err_nomem;
+ return -ENOMEM;
- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables;
ac_rog_tunables->power_limits = ac_limits;
/* Set initial AC values */
@@ -1060,13 +1081,10 @@ static void init_rog_tunables(void)
/* Initialize DC power tunables */
dc_limits = power_data->dc_data;
if (dc_limits) {
- dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
- if (!dc_rog_tunables) {
- kfree(ac_rog_tunables);
- goto err_nomem;
- }
+ dc_rog_tunables = kzalloc_obj(*dc_rog_tunables);
+ if (!dc_rog_tunables)
+ return -ENOMEM;
- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables;
dc_rog_tunables->power_limits = dc_limits;
/* Set initial DC values */
@@ -1106,15 +1124,16 @@ static void init_rog_tunables(void)
pr_debug("No DC PPT limits defined\n");
}
- return;
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables);
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables);
-err_nomem:
- pr_err("Failed to allocate memory for tunables\n");
+ return 0;
}
static int __init asus_fw_init(void)
{
char *wmi_uid;
+ int err;
wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
if (!wmi_uid)
@@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void)
if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI))
return -ENODEV;
- init_rog_tunables();
+ err = init_rog_tunables();
+ if (err)
+ return err;
/* Must always be last step to ensure data is available */
- return asus_fw_attr_add();
+ err = asus_fw_attr_add();
+ if (err)
+ goto err_free_tunables;
+
+ return 0;
+
+err_free_tunables:
+ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
+ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
+ return err;
}
static void __exit asus_fw_exit(void)
diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h
index 322bfa647c2d..0e301d663d0c 100644
--- a/drivers/platform/x86/asus-armoury.h
+++ b/drivers/platform/x86/asus-armoury.h
@@ -371,6 +371,38 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA401KM"),
+ },
+ .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_max = 80,
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .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 = 25,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl3_fppt_max = 65,
+ .ppt_pl3_fppt_min = 45,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "FA401UM"),
},
.driver_data = &(struct power_data) {
@@ -756,6 +788,40 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608WV"),
+ },
+ .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,
+ .nv_tgp_max = 115,
+ .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,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "FA617NS"),
},
.driver_data = &(struct power_data) {
@@ -911,6 +977,32 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "FX607VU"),
},
.driver_data = &(struct power_data) {
@@ -2081,6 +2173,35 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G635LX"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 140,
+ .ppt_pl1_spl_max = 175,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .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 = 150,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "G713PV"),
},
.driver_data = &(struct power_data) {
@@ -2324,6 +2445,35 @@ static const struct dmi_system_id power_limits[] = {
},
{
.matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 35,
+ .ppt_pl1_spl_def = 60,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 40,
+ .ppt_pl2_sppt_def = 70,
+ .ppt_pl2_sppt_max = 95,
+ .ppt_pl3_fppt_min = 50,
+ .ppt_pl3_fppt_def = 85,
+ .ppt_pl3_fppt_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 35,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 60,
+ .ppt_pl2_sppt_min = 40,
+ .ppt_pl2_sppt_def = 55,
+ .ppt_pl2_sppt_max = 70,
+ .ppt_pl3_fppt_min = 50,
+ .ppt_pl3_fppt_def = 65,
+ .ppt_pl3_fppt_max = 85,
+ },
+ },
+ },
+ {
+ .matches = {
DMI_MATCH(DMI_BOARD_NAME, "RC71"),
},
.driver_data = &(struct power_data) {
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
index 140ac8a10537..449addd1ac7a 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -42,8 +42,6 @@
#define ASUS_LAPTOP_VERSION "0.42"
#define ASUS_LAPTOP_NAME "Asus Laptop Support"
-#define ASUS_LAPTOP_CLASS "hotkey"
-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey"
#define ASUS_LAPTOP_FILE KBUILD_MODNAME
#define ASUS_LAPTOP_PREFIX "\\_SB.ATKD."
@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data)
/* TODO Find a better way to handle events count. */
count = asus->event_count[event % 128]++;
- acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
- dev_name(&asus->device->dev), event,
- count);
+ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev),
+ event, count);
if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
event = ATKD_BRNUP;
@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev)
if (!asus)
return -ENOMEM;
asus->handle = device->handle;
- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
asus->device = device;
asus_dmi_check();
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index 8005c088e9ee..aeb461b1644d 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */
{ KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */
{ KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */
{ KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
{ KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
{ KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c
index 2b494bf3cba8..aab45f0442c5 100644
--- a/drivers/platform/x86/asus-wireless.c
+++ b/drivers/platform/x86/asus-wireless.c
@@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev)
const struct acpi_device_id *id;
int err;
+ id = acpi_match_acpi_device(device_ids, adev);
+ if (!id)
+ return -ENODEV;
+
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data);
data->adev = adev;
+ data->hswc_params = (const struct hswc_params *)id->driver_data;
data->idev = devm_input_allocate_device(&pdev->dev);
if (!data->idev)
@@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev)
if (err)
return err;
- id = acpi_match_acpi_device(device_ids, adev);
- if (!id)
- return 0;
-
- data->hswc_params = (const struct hswc_params *)id->driver_data;
-
data->wq = create_singlethread_workqueue("asus_wireless_workqueue");
if (!data->wq)
return -ENOMEM;
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index e835779b6f5f..a65090429ca7 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
return 0;
}
+/*
+ * Returns true if at least one custom fan curve is active
+ *
+ * Used by asus-armoury to check if PPT writes will be accepted by the BIOS
+ * on models that require an active fan curve for TDP changes.
+ */
+bool asus_wmi_custom_fan_curve_is_enabled(void)
+{
+ struct fan_curve_data *curves;
+ struct asus_wmi *asus;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ asus = asus_ref.asus;
+ if (!asus)
+ return false;
+
+ curves = asus->custom_fan_curves;
+
+ return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) ||
+ (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) ||
+ (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled);
+}
+EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI");
+
/* Throttle thermal policy ****************************************************/
static int throttle_thermal_policy_write(struct asus_wmi *asus)
{
@@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev)
return 0;
fail_wmi_handler:
+ asus_screenpad_exit(asus);
+fail_screenpad:
asus_wmi_backlight_exit(asus);
fail_backlight:
asus_wmi_rfkill_exit(asus);
-fail_screenpad:
- asus_screenpad_exit(asus);
fail_rfkill:
asus_wmi_led_exit(asus);
fail_leds:
+fail_custom_fan_curve:
fail_hwmon:
asus_wmi_input_exit(asus);
fail_input:
asus_wmi_sysfs_exit(asus->platform_device);
fail_sysfs:
-fail_custom_fan_curve:
fail_platform_profile_setup:
fail_fan_boost_mode:
fail_platform:
diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c
index 4b7c54b76228..d8c55ec6acd4 100644
--- a/drivers/platform/x86/dell/dell-smbios-base.c
+++ b/drivers/platform/x86/dell/dell-smbios-base.c
@@ -40,7 +40,7 @@ struct smbios_device {
struct list_head list;
struct device *device;
int priority;
- int (*call_fn)(struct calling_interface_buffer *arg);
+ smbios_callback_fn_t call_fn;
};
struct smbios_call {
@@ -146,7 +146,7 @@ int dell_smbios_error(int value)
}
EXPORT_SYMBOL_GPL(dell_smbios_error);
-int dell_smbios_register_device(struct device *d, int priority, void *call_fn)
+int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn)
{
struct smbios_device *priv;
@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer)
goto out_smbios_call;
}
- ret = selected->call_fn(buffer);
+ ret = selected->call_fn(selected->device, buffer);
out_smbios_call:
mutex_unlock(&smbios_mutex);
diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c
index 7055e2c40f34..fc4a674c692e 100644
--- a/drivers/platform/x86/dell/dell-smbios-smm.c
+++ b/drivers/platform/x86/dell/dell-smbios-smm.c
@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy)
}
}
-static int dell_smbios_smm_call(struct calling_interface_buffer *input)
+static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input)
{
struct smi_cmd command;
size_t size;
@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input)
}
/* When enabled this indicates that SMM won't work */
-static bool test_wsmt_enabled(void)
+static bool test_wsmt_enabled(struct device *dev)
{
struct calling_interface_token *wsmt;
@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void)
memset(buffer, 0, sizeof(struct calling_interface_buffer));
buffer->input[0] = wsmt->location;
buffer->output[0] = 99;
- dell_smbios_smm_call(buffer);
+ dell_smbios_smm_call(dev, buffer);
if (buffer->output[0] == 99)
return true;
@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void)
dmi_walk(find_cmd_address, NULL);
- if (test_wsmt_enabled()) {
+ if (test_wsmt_enabled(&platform_device->dev)) {
pr_debug("Disabling due to WSMT enabled\n");
ret = -ENODEV;
goto fail_wsmt;
diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c
index 64d0871b706e..f9443ddfff55 100644
--- a/drivers/platform/x86/dell/dell-smbios-wmi.c
+++ b/drivers/platform/x86/dell/dell-smbios-wmi.c
@@ -6,21 +6,20 @@
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/fs.h>
-#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/uaccess.h>
#include <linux/wmi.h>
#include <uapi/linux/wmi.h>
#include "dell-smbios.h"
#include "dell-wmi-descriptor.h"
-static DEFINE_MUTEX(call_mutex);
-static DEFINE_MUTEX(list_mutex);
static int wmi_supported;
struct misc_bios_flags_structure {
@@ -32,21 +31,16 @@ struct misc_bios_flags_structure {
#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
struct wmi_smbios_priv {
+ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */
struct dell_wmi_smbios_buffer *buf;
- struct list_head list;
struct wmi_device *wdev;
struct device *child;
u64 req_buf_size;
struct miscdevice char_dev;
};
-static LIST_HEAD(wmi_list);
-static inline struct wmi_smbios_priv *get_first_smbios_priv(void)
-{
- return list_first_entry_or_null(&wmi_list,
- struct wmi_smbios_priv,
- list);
-}
+static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */
+static struct wmi_smbios_priv *chardev_priv;
static int run_smbios_call(struct wmi_device *wdev)
{
@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev)
return 0;
}
-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
+static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer)
{
- struct wmi_smbios_priv *priv;
+ struct wmi_smbios_priv *priv = dev_get_drvdata(dev);
size_t difference;
size_t size;
int ret;
- mutex_lock(&call_mutex);
- priv = get_first_smbios_priv();
- if (!priv) {
- ret = -ENODEV;
- goto out_wmi_call;
- }
-
size = sizeof(struct calling_interface_buffer);
difference = priv->req_buf_size - sizeof(u64) - size;
+ guard(mutex)(&priv->call_lock);
+
memset(&priv->buf->ext, 0, difference);
memcpy(&priv->buf->std, buffer, size);
ret = run_smbios_call(priv->wdev);
memcpy(buffer, &priv->buf->std, size);
-out_wmi_call:
- mutex_unlock(&call_mutex);
return ret;
}
-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp)
-{
- struct wmi_smbios_priv *priv;
-
- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev);
- filp->private_data = priv;
-
- return nonseekable_open(inode, filp);
-}
-
static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length,
loff_t *offset)
{
- struct wmi_smbios_priv *priv = filp->private_data;
+ guard(rwsem_read)(&chardev_lock);
+
+ if (!chardev_priv)
+ return -ENODEV;
- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size,
- sizeof(priv->req_buf_size));
+ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size,
+ sizeof(chardev_priv->req_buf_size));
}
static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg;
- struct wmi_smbios_priv *priv = filp->private_data;
- long ret;
if (cmd != DELL_WMI_SMBIOS_CMD)
return -ENOIOCTLCMD;
- mutex_lock(&call_mutex);
- ret = dell_smbios_wmi_do_ioctl(priv, input);
- mutex_unlock(&call_mutex);
+ guard(rwsem_read)(&chardev_lock);
- return ret;
+ if (!chardev_priv)
+ return -ENODEV;
+
+ guard(mutex)(&chardev_priv->call_lock);
+
+ return dell_smbios_wmi_do_ioctl(chardev_priv, input);
}
static const struct file_operations dell_smbios_wmi_fops = {
.owner = THIS_MODULE,
- .open = dell_smbios_wmi_open,
.read = dell_smbios_wmi_read,
.unlocked_ioctl = dell_smbios_wmi_ioctl,
.compat_ioctl = compat_ptr_ioctl,
@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data)
misc_deregister(char_dev);
}
+static void dell_smbios_wmi_clear_chardev(void *data)
+{
+ guard(rwsem_write)(&chardev_lock);
+
+ chardev_priv = NULL;
+}
+
static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv)
{
int ret;
+ scoped_guard(rwsem_write, &chardev_lock) {
+ /* We can only have a single chardev at a time */
+ if (chardev_priv)
+ return -EBUSY;
+
+ chardev_priv = priv;
+ }
+
+ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL);
+ if (ret < 0)
+ return ret;
+
priv->char_dev.minor = MISC_DYNAMIC_MINOR;
priv->char_dev.name = "wmi/dell-smbios";
priv->char_dev.fops = &dell_smbios_wmi_fops;
@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
if (!priv->buf)
return -ENOMEM;
- ret = dell_smbios_wmi_register_chardev(priv);
+ ret = devm_mutex_init(&wdev->dev, &priv->call_lock);
if (ret)
return ret;
- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
+ ret = dell_smbios_wmi_register_chardev(priv);
if (ret)
return ret;
- mutex_lock(&list_mutex);
- list_add_tail(&priv->list, &wmi_list);
- mutex_unlock(&list_mutex);
-
- return 0;
+ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
}
static void dell_smbios_wmi_remove(struct wmi_device *wdev)
{
- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
-
- mutex_lock(&call_mutex);
- mutex_lock(&list_mutex);
- list_del(&priv->list);
- mutex_unlock(&list_mutex);
dell_smbios_unregister_device(&wdev->dev);
- mutex_unlock(&call_mutex);
}
static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = {
.probe = dell_smbios_wmi_probe,
.remove = dell_smbios_wmi_remove,
.id_table = dell_smbios_wmi_id_table,
+ .no_singleton = true,
};
int init_dell_smbios_wmi(void)
diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h
index f421b8533a9e..1e64803bc00d 100644
--- a/drivers/platform/x86/dell/dell-smbios.h
+++ b/drivers/platform/x86/dell/dell-smbios.h
@@ -47,6 +47,8 @@
struct notifier_block;
+typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer);
+
struct calling_interface_token {
u16 tokenID;
u16 location;
@@ -64,7 +66,7 @@ struct calling_interface_structure {
struct calling_interface_token tokens[];
} __packed;
-int dell_smbios_register_device(struct device *d, int priority, void *call_fn);
+int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn);
void dell_smbios_unregister_device(struct device *d);
int dell_smbios_error(int value);
diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c
index 997383ba1846..38a6b3ae2f75 100644
--- a/drivers/platform/x86/dell/dell-wmi-base.c
+++ b/drivers/platform/x86/dell/dell-wmi-base.c
@@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l
key++;
used = 1;
} else if (type == 0x0012 && code == 0x000d && remaining > 0) {
- value = (le16_to_cpu(buffer[2]) == 2);
+ value = (le16_to_cpu(buffer[0]) == 2);
used = 1;
}
@@ -843,9 +843,22 @@ static int __init dell_wmi_init(void)
err = dell_privacy_register_driver();
if (err)
- return err;
+ goto out_smbios;
- return wmi_driver_register(&dell_wmi_driver);
+ err = wmi_driver_register(&dell_wmi_driver);
+ if (err)
+ goto out_privacy;
+
+ return 0;
+
+out_privacy:
+ dell_privacy_unregister_driver();
+
+out_smbios:
+ if (wmi_requires_smbios_request)
+ dell_wmi_events_set_enabled(false);
+
+ return err;
}
late_initcall(dell_wmi_init);
diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c
index 736d9b1fdcfb..f8903ced461b 100644
--- a/drivers/platform/x86/dell/dell-wmi-ddv.c
+++ b/drivers/platform/x86/dell/dell-wmi-ddv.c
@@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met
static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method,
u32 arg, struct dell_wmi_buffer **result)
{
- struct dell_wmi_buffer *buffer;
struct wmi_buffer output;
size_t buffer_size;
int ret;
- ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer));
+ ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer));
if (ret < 0)
return ret;
- buffer = output.data;
- if (!le32_to_cpu(buffer->raw_size)) {
- ret = -ENODATA;
+ struct dell_wmi_buffer *buffer __free(kfree) = output.data;
- goto err_free;
- }
+ if (!le32_to_cpu(buffer->raw_size))
+ return -ENODATA;
buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size));
if (buffer_size > output.length) {
dev_warn(&wdev->dev,
FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n",
buffer_size, output.length);
- ret = -EMSGSIZE;
-
- goto err_free;
+ return -EMSGSIZE;
}
- *result = buffer;
+ *result = no_free_ptr(buffer);
return 0;
-
-err_free:
- kfree(output.data);
-
- return ret;
}
static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method,
diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c
index f9d275b2f900..366e5b8dc868 100644
--- a/drivers/platform/x86/dell/dell-wmi-privacy.c
+++ b/drivers/platform/x86/dell/dell-wmi-privacy.c
@@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void)
{
struct privacy_wmi_data *priv;
- mutex_lock(&list_mutex);
+ guard(mutex)(&list_mutex);
+
priv = list_first_entry_or_null(&wmi_list,
struct privacy_wmi_data,
list);
- mutex_unlock(&list_mutex);
return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO));
}
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
index db278ff4cc4d..154e115af4b4 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
@@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value)
if (ret < 0)
goto out;
- print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
buffer, buffer_size,
SETATTRIBUTE_METHOD_ID);
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
index 3bddedad5eba..eb48ced55823 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
@@ -107,7 +107,7 @@ enum {
static int get_##type##_instance_id(struct kobject *kobj) \
{ \
int i; \
- for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \
+ for (i = 0; i < wmi_priv.type##_instances_count; i++) { \
if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\
return i; \
} \
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index d18a80907611..8a640c25830a 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -34,8 +34,6 @@
#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver"
#define EEEPC_LAPTOP_FILE "eeepc"
-#define EEEPC_ACPI_CLASS "hotkey"
-#define EEEPC_ACPI_DEVICE_NAME "Hotkey"
#define EEEPC_ACPI_HID "ASUS010"
MODULE_AUTHOR("Corentin Chary, Eric Cooper");
@@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data)
if (event > ACPI_MAX_SYS_NOTIFY)
return;
count = eeepc->event_count[event % 128]++;
- acpi_bus_generate_netlink_event(device->pnp.device_class,
- dev_name(&device->dev), event,
+ acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event,
count);
/* Brightness events are special */
@@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev)
if (!eeepc)
return -ENOMEM;
eeepc->handle = device->handle;
- strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
- strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
eeepc->device = device;
platform_set_drvdata(pdev, eeepc);
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
index 54d0b9cec4d3..ea543deef68f 100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -56,7 +56,6 @@
#define FUJITSU_LCD_N_LEVELS 8
-#define ACPI_FUJITSU_CLASS "fujitsu"
#define ACPI_FUJITSU_BL_HID "FUJ02B1"
#define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver"
#define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1"
@@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev)
snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
acpi_device_hid(device));
- priv->input->name = acpi_device_name(device);
+ priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME;
priv->input->phys = priv->phys;
priv->input->id.bustype = BUS_HOST;
priv->input->id.product = 0x06;
@@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev)
return -ENOMEM;
fujitsu_bl = priv;
- strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
platform_set_drvdata(pdev, priv);
- pr_info("ACPI: %s [%s]\n",
- acpi_device_name(device), acpi_device_bid(device));
+ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME,
+ acpi_device_bid(device));
if (get_max_brightness(&pdev->dev) <= 0)
priv->max_brightness = FUJITSU_LCD_N_LEVELS;
@@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev)
snprintf(priv->phys, sizeof(priv->phys), "%s/input0",
acpi_device_hid(device));
- priv->input->name = acpi_device_name(device);
+ priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME;
priv->input->phys = priv->phys;
priv->input->id.bustype = BUS_HOST;
@@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev)
WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
fext = &pdev->dev;
- strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
-
platform_set_drvdata(pdev, priv);
/* kfifo */
@@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev)
if (ret)
return ret;
- pr_info("ACPI: %s [%s]\n",
- acpi_device_name(device), acpi_device_bid(device));
+ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME,
+ acpi_device_bid(device));
while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
i++ < MAX_HOTKEY_RINGBUFFER_SIZE)
diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c
index 2f8c1b89cbca..e1f5dc86dc4d 100644
--- a/drivers/platform/x86/fujitsu-tablet.c
+++ b/drivers/platform/x86/fujitsu-tablet.c
@@ -22,8 +22,6 @@
#define MODULENAME "fujitsu-tablet"
-#define ACPI_FUJITSU_CLASS "fujitsu"
-
#define INVERT_TABLET_MODE_BIT 0x01
#define INVERT_DOCK_STATE_BIT 0x02
#define FORCE_TABLET_MODE_IF_UNDOCK 0x04
@@ -160,6 +158,7 @@ static struct {
struct fujitsu_config config;
unsigned long prev_keymask;
+ char name[17];
char phys[21];
int irq;
@@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev)
if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base)
return -ENODEV;
- sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev));
- sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS);
-
- snprintf(fujitsu.phys, sizeof(fujitsu.phys),
- "%s/input0", acpi_device_hid(adev));
+ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev));
+ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev));
- error = input_fujitsu_setup(&pdev->dev,
- acpi_device_name(adev), fujitsu.phys);
+ error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys);
if (error)
return error;
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
index 27fd6cd21529..309634c1cc20 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -85,7 +85,7 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_
* bytes.
*/
conv_dst_size = size;
- if (size > dst_size)
+ if (size >= dst_size)
conv_dst_size = dst_size - 1;
/*
@@ -661,12 +661,17 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
int ret = 0;
/* Take action appropriate to each ACPI TYPE */
- if (obj->package.count < min_elements) {
- pr_err("ACPI-package does not have enough elements: %d < %d\n",
- obj->package.count, min_elements);
+ if (obj->package.count < COMMON_ELEM_CNT) {
+ pr_err("ACPI-package is missing common elements: %d < %d\n",
+ obj->package.count, COMMON_ELEM_CNT);
goto pack_attr_exit;
}
+ if (obj->package.count < min_elements) {
+ pr_warn("ACPI-package has fewer elements than expected: %d < %d, parsing available elements\n",
+ obj->package.count, min_elements);
+ }
+
elements = obj->package.elements;
/* sanity checking */
@@ -731,26 +736,31 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
switch (attr_type) {
case HPWMI_STRING_TYPE:
ret = hp_populate_string_package_data(elements,
+ obj->package.count,
instance_id,
attr_name_kobj);
break;
case HPWMI_INTEGER_TYPE:
ret = hp_populate_integer_package_data(elements,
+ obj->package.count,
instance_id,
attr_name_kobj);
break;
case HPWMI_ENUMERATION_TYPE:
ret = hp_populate_enumeration_package_data(elements,
+ obj->package.count,
instance_id,
attr_name_kobj);
break;
case HPWMI_ORDERED_LIST_TYPE:
ret = hp_populate_ordered_list_package_data(elements,
+ obj->package.count,
instance_id,
attr_name_kobj);
break;
case HPWMI_PASSWORD_TYPE:
ret = hp_populate_password_package_data(elements,
+ obj->package.count,
instance_id,
attr_name_kobj);
break;
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
index f1eec0e4ba07..ac57d6eab4c3 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
@@ -279,6 +279,9 @@ enum hp_wmi_data_elements {
PSWD_ENCODINGS = 13,
PSWD_IS_SET = 14,
PSWD_ELEM_CNT = 15,
+
+ /* Minimum elements shared by all attribute types (NAME..SECURITY_LEVEL) */
+ COMMON_ELEM_CNT = SECURITY_LEVEL + 1,
};
#define GET_INSTANCE_ID(type) \
@@ -401,6 +404,7 @@ int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
int hp_alloc_string_data(void);
void hp_exit_string_attributes(void);
int hp_populate_string_package_data(union acpi_object *str_obj,
+ int str_obj_count,
int instance_id,
struct kobject *attr_name_kobj);
@@ -411,6 +415,7 @@ int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
int hp_alloc_integer_data(void);
void hp_exit_integer_attributes(void);
int hp_populate_integer_package_data(union acpi_object *integer_obj,
+ int integer_obj_count,
int instance_id,
struct kobject *attr_name_kobj);
@@ -421,6 +426,7 @@ int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
int hp_alloc_enumeration_data(void);
void hp_exit_enumeration_attributes(void);
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
+ int enum_obj_count,
int instance_id,
struct kobject *attr_name_kobj);
@@ -432,6 +438,7 @@ int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr,
int hp_alloc_ordered_list_data(void);
void hp_exit_ordered_list_attributes(void);
int hp_populate_ordered_list_package_data(union acpi_object *order_obj,
+ int order_obj_count,
int instance_id,
struct kobject *attr_name_kobj);
@@ -440,6 +447,7 @@ int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
int instance_id,
struct kobject *attr_name_kobj);
int hp_populate_password_package_data(union acpi_object *password_obj,
+ int password_obj_count,
int instance_id,
struct kobject *attr_name_kobj);
int hp_alloc_password_data(void);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
index af4d1920d488..446dd18d2cee 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
@@ -163,10 +163,11 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
/* Check that both expected and read object type match */
if (expected_enum_types[eloc] != enum_obj[elem].type) {
- pr_err("Error expected type %d for elem %d, but got type %d instead\n",
- expected_enum_types[eloc], elem, enum_obj[elem].type);
+ pr_warn("Unexpected element type at elem %d: expected %d, got %d, skipping\n",
+ elem, expected_enum_types[eloc], enum_obj[elem].type);
kfree(str_value);
- return -EIO;
+ str_value = NULL;
+ continue;
}
/* Assign appropriate element value to corresponding field */
@@ -227,6 +228,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
kfree(str_value);
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case SECURITY_LEVEL:
@@ -280,6 +283,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
kfree(str_value);
str_value = NULL;
}
+ if (size)
+ elem += (size < MAX_VALUES_SIZE ? size : MAX_VALUES_SIZE) - 1;
break;
default:
pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
@@ -300,10 +305,12 @@ exit_enumeration_package:
* Populate all properties of an instance under enumeration attribute
*
* @enum_obj: ACPI object with enumeration data
+ * @enum_obj_count: Number of elements in @enum_obj
* @instance_id: The instance to enumerate
* @attr_name_kobj: The parent kernel object
*/
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
+ int enum_obj_count,
int instance_id,
struct kobject *attr_name_kobj)
{
@@ -312,7 +319,7 @@ int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
enum_data->attr_name_kobj = attr_name_kobj;
hp_populate_enumeration_elements_from_package(enum_obj,
- enum_obj->package.count,
+ enum_obj_count,
instance_id);
hp_update_attribute_permissions(enum_data->common.is_readonly,
&enumeration_current_val);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
index d96e160953e3..a27907066448 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -243,6 +243,8 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
kfree(str_value);
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case SECURITY_LEVEL:
@@ -275,10 +277,12 @@ exit_integer_package:
* Populate all properties of an instance under integer attribute
*
* @integer_obj: ACPI object with integer data
+ * @integer_obj_count: Number of elements in @integer_obj
* @instance_id: The instance to enumerate
* @attr_name_kobj: The parent kernel object
*/
int hp_populate_integer_package_data(union acpi_object *integer_obj,
+ int integer_obj_count,
int instance_id,
struct kobject *attr_name_kobj)
{
@@ -286,7 +290,7 @@ int hp_populate_integer_package_data(union acpi_object *integer_obj,
integer_data->attr_name_kobj = attr_name_kobj;
hp_populate_integer_elements_from_package(integer_obj,
- integer_obj->package.count,
+ integer_obj_count,
instance_id);
hp_update_attribute_permissions(integer_data->common.is_readonly,
&integer_current_val);
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 f09489a085c8..5bf8d40bdf81 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
@@ -145,7 +145,7 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
if (!order_obj)
return -EINVAL;
- for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) {
+ for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT && elem < order_obj_count; elem++, eloc++) {
switch (order_obj[elem].type) {
case ACPI_TYPE_STRING:
@@ -232,6 +232,8 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
kfree(str_value);
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case SECURITY_LEVEL:
@@ -261,7 +263,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
* Ordered list data is stored in hex and comma separated format
* Convert the data and split it to show each element
*/
- ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
+ ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer,
+ order_obj[elem].string.length,
+ &tmpstr, &tmp_len);
if (ret)
goto exit_list;
@@ -298,10 +302,12 @@ exit_list:
* Populate all properties of an instance under ordered_list attribute
*
* @order_obj: ACPI object with ordered_list data
+ * @order_obj_count: Number of elements in @order_obj
* @instance_id: The instance to enumerate
* @attr_name_kobj: The parent kernel object
*/
-int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
+int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int order_obj_count,
+ int instance_id,
struct kobject *attr_name_kobj)
{
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
@@ -309,7 +315,7 @@ int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int inst
ordered_list_data->attr_name_kobj = attr_name_kobj;
hp_populate_ordered_list_elements_from_package(order_obj,
- order_obj->package.count,
+ order_obj_count,
instance_id);
hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
&ordered_list_current_val);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
index 4d79eb8056a5..a9e178637416 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -66,7 +66,7 @@ static int validate_password_input(int instance_id, const char *buf)
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
length = strlen(buf);
- if (buf[length - 1] == '\n')
+ if (length > 0 && buf[length - 1] == '\n')
length--;
if (length > MAX_PASSWD_SIZE)
@@ -123,7 +123,7 @@ static ssize_t new_password_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
- return store_password_instance(kobj, buf, count, true);
+ return store_password_instance(kobj, buf, count, false);
}
static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
@@ -321,6 +321,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case SECURITY_LEVEL:
password_data->common.security_level = int_value;
@@ -351,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
case PSWD_ENCODINGS:
size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE);
for (pos_values = 0; pos_values < size; pos_values++) {
+ if (elem + pos_values >= password_obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
password_obj[elem + pos_values].string.length,
&str_value, &value_len);
@@ -362,6 +369,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case PSWD_IS_SET:
password_data->is_enabled = int_value;
@@ -385,10 +394,12 @@ exit_package:
* Populate all properties for an instance under password attribute
*
* @password_obj: ACPI object with password data
+ * @password_obj_count: Number of elements in @password_obj
* @instance_id: The instance to enumerate
* @attr_name_kobj: The parent kernel object
*/
-int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id,
+int hp_populate_password_package_data(union acpi_object *password_obj, int password_obj_count,
+ int instance_id,
struct kobject *attr_name_kobj)
{
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
@@ -396,7 +407,7 @@ int hp_populate_password_package_data(union acpi_object *password_obj, int insta
password_data->attr_name_kobj = attr_name_kobj;
hp_populate_password_elements_from_package(password_obj,
- password_obj->package.count,
+ password_obj_count,
instance_id);
hp_friendly_user_name_update(password_data->common.path,
diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
index 2b00a14792e9..4d94e48c1a4c 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
@@ -238,7 +238,7 @@ static ssize_t sk_store(struct kobject *kobj,
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
HPWMI_SECUREPLATFORM,
(void *)bioscfg_drv.spm_data.signing_key,
- count, 0);
+ length, 0);
if (!ret) {
bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
@@ -274,7 +274,7 @@ static ssize_t kek_store(struct kobject *kobj,
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
HPWMI_SECUREPLATFORM,
(void *)bioscfg_drv.spm_data.endorsement_key,
- count, 0);
+ length, 0);
if (!ret) {
bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
index fe5a9a3a4ef1..f3dfba270f7e 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -233,6 +233,8 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
kfree(str_value);
str_value = NULL;
}
+ if (size)
+ elem += size - 1;
break;
case SECURITY_LEVEL:
@@ -263,10 +265,12 @@ exit_string_package:
* Populate all properties of an instance under string attribute
*
* @string_obj: ACPI object with string data
+ * @string_obj_count: Number of elements in @string_obj
* @instance_id: The instance to enumerate
* @attr_name_kobj: The parent kernel object
*/
int hp_populate_string_package_data(union acpi_object *string_obj,
+ int string_obj_count,
int instance_id,
struct kobject *attr_name_kobj)
{
@@ -275,7 +279,7 @@ int hp_populate_string_package_data(union acpi_object *string_obj,
string_data->attr_name_kobj = attr_name_kobj;
hp_populate_string_elements_from_package(string_obj,
- string_obj->package.count,
+ string_obj_count,
instance_id);
hp_update_attribute_permissions(string_data->common.is_readonly,
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index 8ba286ed8721..b2773fc1aca4 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -14,6 +14,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/dmi.h>
@@ -23,6 +25,7 @@
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
+#include <linux/kstrtox.h>
#include <linux/limits.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -58,6 +61,12 @@ enum hp_ec_offsets {
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
#define HP_POWER_LIMIT_NO_CHANGE 0xFF
+#define HPWMI_MUX_MODE_UMA BIT(0)
+#define HPWMI_MUX_MODE_HYBRID BIT(1)
+#define HPWMI_MUX_MODE_DISCRETE BIT(2)
+#define HPWMI_MUX_MODE_OPTIMUS BIT(3)
+#define HPWMI_MUX_MODE_MASK GENMASK(6, 0)
+#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE)
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
@@ -133,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = {
.ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET,
};
-/*
- * A generic pointer for the currently-active board's thermal profile
- * parameters.
- */
-static struct thermal_profile_params *active_thermal_profile_params;
+struct hp_wmi_fan_profile_params {
+ int (*get_fan_speed)(int fan);
+ bool fan_table;
+};
+
+struct hp_wmi_board_params {
+ const struct thermal_profile_params *thermal_profile;
+ const struct hp_wmi_fan_profile_params *fan_profile;
+};
+
+static int hp_wmi_get_fan_speed_victus_s(int fan);
+
+static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = {
+ .get_fan_speed = hp_wmi_get_fan_speed_victus_s,
+ .fan_table = true,
+};
+
+static const struct hp_wmi_board_params victus_s_board_params = {
+ .thermal_profile = &victus_s_thermal_params,
+ .fan_profile = &victus_s_fan_profile_params,
+};
+
+static const struct hp_wmi_board_params omen_v1_board_params = {
+ .thermal_profile = &omen_v1_thermal_params,
+ .fan_profile = &victus_s_fan_profile_params,
+};
+
+static const struct hp_wmi_board_params omen_v1_legacy_board_params = {
+ .thermal_profile = &omen_v1_legacy_thermal_params,
+ .fan_profile = &victus_s_fan_profile_params,
+};
+
+static const struct hp_wmi_board_params omen_v1_no_ec_board_params = {
+ .thermal_profile = &omen_v1_no_ec_thermal_params,
+ .fan_profile = &victus_s_fan_profile_params,
+};
+
+static const struct hp_wmi_board_params *active_board_params;
+
+static const struct thermal_profile_params *hp_wmi_thermal_profile(void)
+{
+ if (!active_board_params)
+ return NULL;
+
+ return active_board_params->thermal_profile;
+}
/* DMI board names of devices that should use the omen specific path for
* thermal profiles.
@@ -157,7 +207,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",
+ "8A15", "8A42", "8A43",
"8BAD",
"8C58",
"8E41",
@@ -187,87 +237,111 @@ static const char * const victus_thermal_profile_boards[] = {
"8A25",
};
-/* DMI Board names of Victus 16-r and Victus 16-s laptops */
-static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
+/* DMI board-specific feature data for Omen and Victus laptops. */
+static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") },
- .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ .driver_data = (void *)&omen_v1_legacy_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") },
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") },
- .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ .driver_data = (void *)&omen_v1_legacy_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") },
- .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ .driver_data = (void *)&omen_v1_legacy_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") },
+ .driver_data = (void *)&omen_v1_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") },
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
- .driver_data = (void *)&omen_v1_thermal_params,
+ .driver_data = (void *)&omen_v1_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
- .driver_data = (void *)&victus_s_thermal_params,
+ .driver_data = (void *)&victus_s_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") },
- .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ .driver_data = (void *)&omen_v1_legacy_board_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
- .driver_data = (void *)&omen_v1_no_ec_thermal_params,
+ .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,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") },
.driver_data = (void *)&omen_v1_no_ec_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
- .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ .driver_data = (void *)&omen_v1_legacy_board_params,
},
{},
};
@@ -332,6 +406,7 @@ enum hp_wmi_commandtype {
HPWMI_POSTCODEERROR_QUERY = 0x2a,
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
+ HPWMI_GRAPHICS_MUX_QUERY = 0x52,
};
struct victus_power_limits {
@@ -500,9 +575,9 @@ struct hp_wmi_hwmon_priv {
struct mutex lock; /* protects mode, pwm */
u8 min_rpm;
u8 max_rpm;
- int gpu_delta;
u8 mode;
- u8 pwm;
+ u8 cpu_pwm;
+ u8 gpu_pwm;
struct delayed_work keep_alive_dwork;
};
@@ -829,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled)
return enabled;
}
-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv)
{
u8 fan_speed[2];
- int gpu_speed, ret;
+ int ret;
- fan_speed[CPU_FAN] = speed;
- fan_speed[GPU_FAN] = speed;
+ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC)
+ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
+ else
+ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv);
- /*
- * GPU fan speed is always a little higher than CPU fan speed, we fetch
- * this delta value from the fan table during hwmon init.
- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
- * automatic mode.
- */
- if (speed != HP_FAN_SPEED_AUTOMATIC) {
- gpu_speed = speed + priv->gpu_delta;
- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
- }
+ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC)
+ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
+ else
+ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv);
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
@@ -863,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
{
- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
+ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC;
+ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC;
+ return hp_wmi_fan_speed_set(priv);
}
static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
@@ -1124,12 +1197,134 @@ static int camera_shutter_input_setup(void)
return err;
}
+static const u8 mux_bitmask_map[] = {
+ [0] = HPWMI_MUX_MODE_HYBRID,
+ [1] = HPWMI_MUX_MODE_DISCRETE,
+ [2] = HPWMI_MUX_MODE_OPTIMUS,
+ [3] = HPWMI_MUX_MODE_UMA,
+};
+
+static int hp_wmi_get_mux_supported_modes(u8 *supported)
+{
+ u8 legacy_buffer[4] = {};
+ u8 buffer[128] = {};
+ u32 req_packet = 0;
+ int ret;
+
+ if (!supported)
+ return -EINVAL;
+
+ /* Try modern BIOS design data query (128-byte buffer) */
+ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
+ buffer, zero_if_sup(req_packet), sizeof(buffer));
+ if (ret == 0) {
+ *supported = buffer[7];
+ return 0;
+ }
+
+ /*
+ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub.
+ * If the modern query is not supported, check if the MUX query endpoint
+ * responds to a read request. If it succeeds, the hardware has MUX
+ * capability but lacks the mode map, defaulting to Hybrid + Discrete.
+ */
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ legacy_buffer, sizeof(legacy_buffer), 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ *supported = HPWMI_MUX_LEGACY_MASK;
+ return 0;
+}
+
+static int hp_wmi_get_mux_mode(u8 *mode)
+{
+ u8 buffer[4] = {};
+ int ret;
+
+ if (!mode)
+ return -EINVAL;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ buffer, sizeof(buffer), sizeof(buffer));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ /* Mask the highest bit, which might be used as a BIOS status flag */
+ *mode = buffer[0] & HPWMI_MUX_MODE_MASK;
+ return 0;
+}
+
+static int hp_wmi_set_mux_mode(u8 mode)
+{
+ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 };
+ int ret;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE,
+ buffer, sizeof(buffer), sizeof(buffer));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static ssize_t gpu_mux_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 mode;
+ int ret;
+
+ ret = hp_wmi_get_mux_mode(&mode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", mode);
+}
+
+static ssize_t gpu_mux_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ u32 requested;
+ u8 supported;
+ int ret;
+
+ ret = kstrtou32(buf, 0, &requested);
+ if (ret)
+ return ret;
+ if (requested >= ARRAY_SIZE(mux_bitmask_map))
+ return -EINVAL;
+
+ ret = hp_wmi_get_mux_supported_modes(&supported);
+ if (ret)
+ return ret;
+
+ /* Verify if the requested mode is allowed by the hardware mask */
+ if (!(supported & mux_bitmask_map[requested]))
+ return -EOPNOTSUPP;
+
+ ret = hp_wmi_set_mux_mode(requested);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
static DEVICE_ATTR_RO(display);
static DEVICE_ATTR_RO(hddtemp);
static DEVICE_ATTR_RW(als);
static DEVICE_ATTR_RO(dock);
static DEVICE_ATTR_RO(tablet);
static DEVICE_ATTR_RW(postcode);
+static DEVICE_ATTR_RW(gpu_mux_mode);
static struct attribute *hp_wmi_attrs[] = {
&dev_attr_display.attr,
@@ -1138,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = {
&dev_attr_dock.attr,
&dev_attr_tablet.attr,
&dev_attr_postcode.attr,
+ &dev_attr_gpu_mux_mode.attr,
NULL,
};
ATTRIBUTE_GROUPS(hp_wmi);
@@ -1789,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void)
return is_victus_s_board;
}
+static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void)
+{
+ if (!active_board_params)
+ return NULL;
+
+ return active_board_params->fan_profile;
+}
+
+static bool hp_wmi_fan_control_supported(void)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ return params && params->get_fan_speed;
+}
+
+static bool hp_wmi_fan_table_supported(void)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ return params && params->fan_table;
+}
+
+static int hp_wmi_get_active_fan_speed(int fan)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ if (!params || !params->get_fan_speed)
+ return -EOPNOTSUPP;
+
+ return params->get_fan_speed(fan);
+}
+
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
bool *ppab_enable,
u8 *dstate,
@@ -1874,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
u8 current_dstate, current_gpu_slowdown_temp, tp;
const struct thermal_profile_params *params;
- params = active_thermal_profile_params;
+ params = hp_wmi_thermal_profile();
+ if (!params)
+ return -ENODEV;
+
if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
*profile = active_platform_profile;
@@ -1886,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
return ret;
/*
- * We cannot use active_thermal_profile_params here, because boards
- * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
- * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
- * Gaming Hub on windows. Hence accept both of these values.
+ * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot,
+ * but logically it should have tp == 0x30 || tp == 0x31, as
+ * corrected by the Omen Gaming Hub on windows. Hence accept both
+ * of these values.
*/
if (tp == victus_s_thermal_params.performance ||
tp == omen_v1_thermal_params.performance) {
@@ -1924,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
{
- struct thermal_profile_params *params;
+ const struct thermal_profile_params *params;
bool gpu_ctgp_enable, gpu_ppab_enable;
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
int err, tp;
- params = active_thermal_profile_params;
+ params = hp_wmi_thermal_profile();
if (!params)
return -ENODEV;
@@ -2195,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = {
static int thermal_profile_setup(struct platform_device *device)
{
const struct platform_profile_ops *ops;
+ const struct thermal_profile_params *params;
int err, tp;
if (is_omen_thermal_profile()) {
@@ -2226,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device)
ops = &platform_profile_victus_ops;
} else if (is_victus_s_thermal_profile()) {
+ params = hp_wmi_thermal_profile();
+ if (!params)
+ return -ENODEV;
+
/*
* For an unknown EC layout board, platform_profile_victus_s_get_ec(),
* behaves like a wrapper around active_platform_profile, to avoid using
* uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
*/
- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
- active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
+ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
active_platform_profile = PLATFORM_PROFILE_BALANCED;
} else {
err = platform_profile_victus_s_get_ec(&active_platform_profile);
@@ -2400,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
switch (priv->mode) {
case PWM_MODE_MAX:
- if (is_victus_s_thermal_profile()) {
+ if (hp_wmi_fan_control_supported()) {
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
return ret;
@@ -2412,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_MANUAL:
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
+ ret = hp_wmi_fan_speed_set(priv);
if (ret < 0)
return ret;
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_AUTO:
- if (is_victus_s_thermal_profile()) {
+ if (hp_wmi_fan_control_supported()) {
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
return ret;
@@ -2445,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
{
switch (type) {
case hwmon_pwm:
- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
+ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported())
return 0;
return 0644;
case hwmon_fan:
- if (is_victus_s_thermal_profile()) {
- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0)
+ if (hp_wmi_fan_control_supported()) {
+ if (hp_wmi_get_active_fan_speed(channel) >= 0)
return 0444;
} else {
if (hp_wmi_get_fan_speed(channel) >= 0)
@@ -2474,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
- if (is_victus_s_thermal_profile())
- ret = hp_wmi_get_fan_speed_victus_s(channel);
+ if (hp_wmi_fan_control_supported())
+ ret = hp_wmi_get_active_fan_speed(channel);
else
ret = hp_wmi_get_fan_speed(channel);
if (ret < 0)
@@ -2484,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
return 0;
case hwmon_pwm:
if (attr == hwmon_pwm_input) {
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
- rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ rpm = hp_wmi_get_active_fan_speed(channel);
if (rpm < 0)
return rpm;
*val = rpm_to_pwm(rpm / 100, priv);
@@ -2514,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct hp_wmi_hwmon_priv *priv;
- int rpm;
+ int cpu_rpm, gpu_rpm;
priv = dev_get_drvdata(dev);
guard(mutex)(&priv->lock);
switch (type) {
case hwmon_pwm:
if (attr == hwmon_pwm_input) {
- if (!is_victus_s_thermal_profile())
+ int rpm;
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
/* PWM input is invalid when not in manual mode */
if (priv->mode != PWM_MODE_MANUAL)
@@ -2530,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
/* ensure PWM input is within valid fan speeds */
rpm = pwm_to_rpm(val, priv);
rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
- priv->pwm = rpm_to_pwm(rpm, priv);
+ if (channel == CPU_FAN)
+ priv->cpu_pwm = rpm_to_pwm(rpm, priv);
+ else if (channel == GPU_FAN)
+ priv->gpu_pwm = rpm_to_pwm(rpm, priv);
return hp_wmi_apply_fan_settings(priv);
}
switch (val) {
@@ -2538,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
priv->mode = PWM_MODE_MAX;
return hp_wmi_apply_fan_settings(priv);
case PWM_MODE_MANUAL:
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
/*
* When switching to manual mode, set fan speed to
* current RPM values to ensure a smooth transition.
*/
- rpm = hp_wmi_get_fan_speed_victus_s(channel);
- if (rpm < 0)
- return rpm;
- priv->pwm = rpm_to_pwm(rpm / 100, priv);
+ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN);
+ if (cpu_rpm < 0)
+ return cpu_rpm;
+ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN);
+ if (gpu_rpm < 0)
+ return gpu_rpm;
+ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv);
+ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv);
priv->mode = PWM_MODE_MANUAL;
return hp_wmi_apply_fan_settings(priv);
case PWM_MODE_AUTO:
@@ -2563,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT),
NULL
};
@@ -2604,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
struct victus_s_fan_table *fan_table;
u8 min_rpm, max_rpm;
u8 cpu_rpm, gpu_rpm, noise_db;
- int gpu_delta, i, num_entries, ret;
+ int i, num_entries, ret;
size_t header_size, entry_size;
/* Default behaviour on hwmon init is automatic mode */
priv->mode = PWM_MODE_AUTO;
- /* Bypass all non-Victus S devices */
- if (!is_victus_s_thermal_profile())
+ /* Bypass devices without fan control support. */
+ if (!hp_wmi_fan_table_supported())
return 0;
ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
@@ -2650,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
if (min_rpm == U8_MAX || max_rpm == 0)
return -EINVAL;
- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
priv->min_rpm = min_rpm;
priv->max_rpm = max_rpm;
- priv->gpu_delta = gpu_delta;
return 0;
}
@@ -2693,24 +2935,25 @@ static int hp_wmi_hwmon_init(void)
return 0;
}
-static void __init setup_active_thermal_profile_params(void)
+static void __init setup_active_board_params(void)
{
const struct dmi_system_id *id;
+ const struct thermal_profile_params *params;
- /*
- * Currently only victus_s devices use the
- * active_thermal_profile_params
- */
- id = dmi_first_match(victus_s_thermal_profile_boards);
+ id = dmi_first_match(hp_wmi_feature_boards);
if (id) {
+ active_board_params = id->driver_data;
+ params = hp_wmi_thermal_profile();
+ if (!params)
+ return;
+
/*
* Marking this boolean is required to ensure that
* is_victus_s_thermal_profile() behaves like a valid
* wrapper.
*/
is_victus_s_board = true;
- active_thermal_profile_params = id->driver_data;
- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
dmi_get_system_info(DMI_BOARD_NAME));
}
@@ -2745,10 +2988,10 @@ static int __init hp_wmi_init(void)
}
/*
- * Setup active board's thermal profile parameters before
- * starting platform driver probe.
+ * Setup active board feature data before starting platform
+ * driver probe.
*/
- setup_active_thermal_profile_params();
+ setup_active_board_params();
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
if (err)
goto err_unregister_device;
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 93cca17fdf58..d6aaf14d66a5 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -6,6 +6,7 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev)
/* Fn lock */
+/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */
+#define FN_LOCK_ACPI_OFF 1
+#define FN_LOCK_ACPI_ON 2
+#define FN_LOCK_ACPI_STAT_OK 0
+
+/*
+ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct
+ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control
+ * Fn-lock via EC registers 0x6B (read) and 0x6C (write).
+ *
+ * GFRS response buffer layout:
+ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success)
+ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on)
+ *
+ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)):
+ * Value is read from byte[2] of the integer argument, so it must be
+ * passed as (value << 16):
+ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C)
+ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C)
+ */
+
+static int huawei_acpi_fn_lock_get(int *on)
+{
+ union acpi_object acpi_arg;
+ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ acpi_arg.type = ACPI_TYPE_INTEGER;
+ acpi_arg.integer.value = 0;
+
+ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ union acpi_object *obj __free(kfree) = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2)
+ return -ENODATA;
+
+ /* byte[0] = STAT, byte[1] = fn-lock state */
+ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
+ return -EIO;
+
+ switch (obj->buffer.pointer[1]) {
+ case FN_LOCK_ACPI_OFF:
+ if (on)
+ *on = 0;
+ break;
+ case FN_LOCK_ACPI_ON:
+ if (on)
+ *on = 1;
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int huawei_acpi_fn_lock_set(int on)
+{
+ union acpi_object acpi_arg;
+ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ /*
+ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02).
+ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off)
+ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on)
+ */
+ acpi_arg.type = ACPI_TYPE_INTEGER;
+ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16;
+
+ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ union acpi_object *obj __free(kfree) = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1)
+ return -ENODATA;
+
+ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
+ return -EIO;
+
+ return 0;
+}
+
static int huawei_wmi_fn_lock_get(int *on)
{
u8 ret[0x100] = { 0 };
int err, i;
+ /* Newer models: use direct ACPI \GFRS method */
+ if (acpi_has_method(NULL, "\\GFRS"))
+ return huawei_acpi_fn_lock_get(on);
+
+ /* WMI fallback */
err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);
if (err)
return err;
@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on)
{
union hwmi_arg arg;
+ /* Newer models: use direct ACPI \SFRS method */
+ if (acpi_has_method(NULL, "\\SFRS"))
+ return huawei_acpi_fn_lock_set(on);
+
+ /* WMI fallback */
arg.cmd = FN_LOCK_SET;
arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.
diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c
index 849f7b415c1e..f506155f35d4 100644
--- a/drivers/platform/x86/intel/int1092/intel_sar.c
+++ b/drivers/platform/x86/intel/int1092/intel_sar.c
@@ -91,8 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob
item->package.count <= data->total_dev_mode)
return AE_ERROR;
- data->device_mode_info = kmalloc_objs(struct wwan_device_mode_info,
- data->total_dev_mode);
+ data->device_mode_info = devm_kcalloc(&context->sar_device->dev,
+ data->total_dev_mode,
+ sizeof(*data->device_mode_info),
+ GFP_KERNEL);
if (!data->device_mode_info)
return AE_ERROR;
@@ -253,7 +255,7 @@ static int sar_probe(struct platform_device *device)
if (!handle)
return -ENODEV;
- context = kzalloc_obj(*context);
+ context = devm_kzalloc(&device->dev, sizeof(*context), GFP_KERNEL);
if (!context)
return -ENOMEM;
@@ -264,7 +266,7 @@ static int sar_probe(struct platform_device *device)
result = guid_parse(SAR_DSM_UUID, &context->guid);
if (result) {
dev_err(&device->dev, "SAR UUID parse error: %d\n", result);
- goto r_free;
+ return result;
}
for (reg = 0; reg < MAX_REGULATORY; reg++)
@@ -272,43 +274,29 @@ static int sar_probe(struct platform_device *device)
if (sar_get_device_mode(device) != AE_OK) {
dev_err(&device->dev, "Failed to get device mode\n");
- result = -EIO;
- goto r_free;
+ return -EIO;
}
result = sysfs_create_group(&device->dev.kobj, &intcsar_group);
if (result) {
dev_err(&device->dev, "sysfs creation failed\n");
- goto r_free;
+ return result;
}
if (acpi_install_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY,
sar_notify, (void *)device) != AE_OK) {
dev_err(&device->dev, "Failed acpi_install_notify_handler\n");
- result = -EIO;
- goto r_sys;
+ sysfs_remove_group(&device->dev.kobj, &intcsar_group);
+ return -EIO;
}
return 0;
-
-r_sys:
- sysfs_remove_group(&device->dev.kobj, &intcsar_group);
-r_free:
- kfree(context);
- return result;
}
static void sar_remove(struct platform_device *device)
{
- struct wwan_sar_context *context = dev_get_drvdata(&device->dev);
- int reg;
-
acpi_remove_notify_handler(ACPI_HANDLE(&device->dev),
ACPI_DEVICE_NOTIFY, sar_notify);
sysfs_remove_group(&device->dev.kobj, &intcsar_group);
- for (reg = 0; reg < MAX_REGULATORY; reg++)
- kfree(context->config_data[reg].device_mode_info);
-
- kfree(context);
}
static struct platform_driver sar_driver = {
diff --git a/drivers/platform/x86/intel/ishtp_eclite.c b/drivers/platform/x86/intel/ishtp_eclite.c
index 93ac8b2dbf38..bca7e217878b 100644
--- a/drivers/platform/x86/intel/ishtp_eclite.c
+++ b/drivers/platform/x86/intel/ishtp_eclite.c
@@ -600,13 +600,16 @@ static int ecl_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
rv = acpi_opregion_init(opr_dev);
if (rv) {
dev_err(cl_data_to_dev(opr_dev), "ACPI opregion init failed\n");
- goto err_exit;
+ goto err_put;
}
/* Reprobe devices depending on ECLite - battery, fan, etc. */
acpi_dev_clear_dependencies(opr_dev->adev);
return 0;
+
+err_put:
+ acpi_dev_put(opr_dev->adev);
err_exit:
ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING);
ishtp_cl_disconnect(ecl_ishtp_cl);
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 825ba5fa0bcb..9f77c0716e59 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
{
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
- int ret, lpm_size;
+ int ret = 0, lpm_size;
u8 mode;
lpm_indices = pmc->map->lpm_reg_index;
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
index 5ded0e25b6c7..bb0d0b8fc54a 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -336,8 +336,11 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
int i;
pd_info->perf_levels = devm_kcalloc(dev, levels, sizeof(struct perf_level), GFP_KERNEL);
- if (!pd_info->perf_levels)
- return 0;
+ if (!pd_info->perf_levels) {
+ pd_info->pp_header.allowed_level_mask = 0;
+ pd_info->pp_header.level_en_mask = 0;
+ return -ENOMEM;
+ }
pd_info->ratio_unit = pd_info->pp_header.ratio_unit;
pd_info->avx_levels = SST_MAX_AVX_LEVELS;
@@ -367,7 +370,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info)
{
struct device *dev = &auxdev->dev;
- int i, mask, levels;
+ int i, ret, mask, levels;
*((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base);
pd_info->sst_header.cp_offset *= 8;
@@ -399,8 +402,12 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai
levels = i;
mask <<= 1;
}
+
+ ret = sst_add_perf_profiles(auxdev, pd_info, levels + 1);
+ if (ret)
+ return ret;
+
pd_info->max_level = levels;
- sst_add_perf_profiles(auxdev, pd_info, levels + 1);
return 0;
}
@@ -599,6 +606,9 @@ static bool disable_dynamic_sst_features(void)
#define SST_CP_PRIORITY_TYPE_START 1
#define SST_CP_PRIORITY_TYPE_WIDTH 1
+#define SST_CP_MAX_ENABLE 1
+#define SST_CP_MAX_PRIORITY_TYPE 1
+
static long isst_if_core_power_state(void __user *argp)
{
struct tpmi_per_power_domain_info *power_domain_info;
@@ -618,6 +628,10 @@ static long isst_if_core_power_state(void __user *argp)
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
+ if (core_power.enable > SST_CP_MAX_ENABLE ||
+ core_power.priority_type > SST_CP_MAX_PRIORITY_TYPE)
+ return -EINVAL;
+
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
_write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
@@ -649,6 +663,11 @@ static long isst_if_core_power_state(void __user *argp)
#define SST_CLOS_CONFIG_MAX_START 16
#define SST_CLOS_CONFIG_MAX_WIDTH 8
+#define SST_MAX_CLOS 3
+
+#define SST_MAX_FREQ 0xff
+#define SST_CLOS_MAX_PRIORITY 0x0f
+
static long isst_if_clos_param(void __user *argp)
{
struct tpmi_per_power_domain_info *power_domain_info;
@@ -657,6 +676,9 @@ static long isst_if_clos_param(void __user *argp)
if (copy_from_user(&clos_param, argp, sizeof(clos_param)))
return -EFAULT;
+ if (clos_param.clos > SST_MAX_CLOS)
+ return -EINVAL;
+
power_domain_info = get_instance(clos_param.socket_id, clos_param.power_domain_id);
if (!power_domain_info)
return -EINVAL;
@@ -665,6 +687,15 @@ static long isst_if_clos_param(void __user *argp)
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
+ if (!in_range(clos_param.min_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1))
+ return -EINVAL;
+
+ if (!in_range(clos_param.max_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1))
+ return -EINVAL;
+
+ if (!in_range(clos_param.prop_prio, 0, SST_CLOS_MAX_PRIORITY + 1))
+ return -EINVAL;
+
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
(SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH,
@@ -703,6 +734,8 @@ static long isst_if_clos_param(void __user *argp)
#define SST_CLOS_ASSOC_CPUS_PER_REG 16
#define SST_CLOS_ASSOC_BITS_PER_CPU 4
+#define SST_CLOS_ASSOC_MAX_LOGICAL_CPU 63
+
static long isst_if_clos_assoc(void __user *argp)
{
struct isst_if_clos_assoc_cmds assoc_cmds;
@@ -729,7 +762,13 @@ static long isst_if_clos_assoc(void __user *argp)
if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc)))
return -EFAULT;
- if (clos_assoc.socket_id > topology_max_packages())
+ if (clos_assoc.clos > SST_MAX_CLOS)
+ return -EINVAL;
+
+ if (clos_assoc.socket_id >= topology_max_packages())
+ return -EINVAL;
+
+ if (clos_assoc.logical_cpu > SST_CLOS_ASSOC_MAX_LOGICAL_CPU)
return -EINVAL;
cpu = clos_assoc.logical_cpu;
@@ -747,6 +786,8 @@ static long isst_if_clos_assoc(void __user *argp)
pkg_id = clos_assoc.socket_id;
sst_inst = isst_common.sst_inst[pkg_id];
+ if (!sst_inst)
+ return -EINVAL;
punit_id = map_partition_power_domain_id(sst_inst, punit_id, &part);
if (punit_id < 0)
@@ -845,6 +886,7 @@ static long isst_if_clos_assoc(void __user *argp)
#define SST_PP_FEATURE_STATE_START 8
#define SST_PP_FEATURE_STATE_WIDTH 8
+#define SST_PP_FEATURE_STATE_VALID_MASK GENMASK(1, 0)
#define SST_BF_FEATURE_SUPPORTED_START 12
#define SST_BF_FEATURE_SUPPORTED_WIDTH 1
@@ -877,7 +919,7 @@ static int isst_if_get_perf_level(void __user *argp)
SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE)
perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1));
- level_mask = perf_level.level_mask;
+ level_mask = perf_level.level_mask & power_domain_info->pp_header.level_en_mask;
perf_level.sst_bf_support = 0;
for_each_set_bit(level, &level_mask, BITS_PER_BYTE) {
/*
@@ -932,6 +974,9 @@ static int isst_if_set_perf_level(void __user *argp)
if (!power_domain_info)
return -EINVAL;
+ if (perf_level.level > power_domain_info->max_level)
+ return -EINVAL;
+
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
@@ -995,6 +1040,9 @@ static int isst_if_set_perf_feature(void __user *argp)
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
+ if (perf_feature.feature & ~SST_PP_FEATURE_STATE_VALID_MASK)
+ return -EINVAL;
+
_write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH,
SST_MUL_FACTOR_NONE)
@@ -1261,6 +1309,12 @@ static int isst_if_get_perf_level_mask(void __user *argp)
if (!power_domain_info)
return -EINVAL;
+ if (cpumask.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level)))
+ return -EINVAL;
+
_read_pp_level_info("mask", mask, cpumask.level, SST_PP_INFO_2_OFFSET,
SST_PP_RSLVD_CORE_MASK_START, SST_PP_RSLVD_CORE_MASK_WIDTH,
SST_MUL_FACTOR_NONE)
@@ -1306,6 +1360,9 @@ static int isst_if_get_base_freq_info(void __user *argp)
if (base_freq.level > power_domain_info->max_level)
return -EINVAL;
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(base_freq.level)))
+ return -EINVAL;
+
_read_bf_level_info("p1_high", base_freq.high_base_freq_mhz, base_freq.level,
SST_BF_INFO_0_OFFSET, SST_BF_P1_HIGH_START, SST_BF_P1_HIGH_WIDTH,
SST_MUL_FACTOR_FREQ)
@@ -1342,6 +1399,12 @@ static int isst_if_get_base_freq_mask(void __user *argp)
if (!power_domain_info)
return -EINVAL;
+ if (cpumask.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level)))
+ return -EINVAL;
+
_read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET,
P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH,
SST_MUL_FACTOR_NONE)
@@ -1370,6 +1433,8 @@ static int isst_if_get_tpmi_instance_count(void __user *argp)
return -EINVAL;
sst_inst = isst_common.sst_inst[tpmi_inst.socket_id];
+ if (!sst_inst)
+ return -EINVAL;
tpmi_inst.count = isst_instance_count(sst_inst);
@@ -1437,6 +1502,9 @@ static int isst_if_get_turbo_freq_info(void __user *argp)
if (turbo_freq.level > power_domain_info->max_level)
return -EINVAL;
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(turbo_freq.level)))
+ return -EINVAL;
+
turbo_freq.max_buckets = TRL_MAX_BUCKETS;
turbo_freq.max_trl_levels = TRL_MAX_LEVELS;
turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS;
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 4443f40ef8aa..516594993073 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 91a9370f11b3..12816d8d5e24 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 4fbc904f1fc3..8213524504ee 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init)
static void __exit ideapad_laptop_exit(void)
{
- ideapad_wmi_driver_unregister();
platform_driver_unregister(&ideapad_acpi_driver);
+ ideapad_wmi_driver_unregister();
}
module_exit(ideapad_laptop_exit)
diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c
index e215e86e3db7..a0e3fa766e37 100644
--- a/drivers/platform/x86/lenovo/think-lmi.c
+++ b/drivers/platform/x86/lenovo/think-lmi.c
@@ -438,14 +438,13 @@ static ssize_t current_password_store(struct kobject *kobj,
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
size_t pwdlen;
- pwdlen = strlen(buf);
+ /* Strip newline; setting password won't work if one is present. */
+ pwdlen = strchrnul(buf, '\n') - buf;
/* pwdlen == 0 is allowed to clear the password */
if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen)))
return -EINVAL;
- strscpy(setting->password, buf, setting->maxlen);
- /* Strip out CR if one is present, setting password won't work if it is present */
- strreplace(setting->password, '\n', '\0');
+ strscpy(setting->password, buf, pwdlen + 1);
return count;
}
@@ -745,6 +744,8 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att
return -EOPNOTSUPP;
for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) {
+ ssize_t ret;
+
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) {
/* Format: 'SVC | SMC, Thumbtype' */
wmistr = kasprintf(GFP_KERNEL, "%s,%s",
@@ -756,8 +757,12 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att
}
if (!wmistr)
return -ENOMEM;
- count += cert_thumbprint(buf, wmistr, count);
+
+ ret = cert_thumbprint(buf, wmistr, count);
kfree(wmistr);
+ if (ret < 0)
+ return ret;
+ count = ret;
}
return count;
@@ -1456,6 +1461,10 @@ static void tlmi_release_attr(void)
/* Free up any saved signatures */
kfree(tlmi_priv.pwd_admin->signature);
kfree(tlmi_priv.pwd_admin->save_signature);
+ if (tlmi_priv.pwd_system) {
+ kfree(tlmi_priv.pwd_system->signature);
+ kfree(tlmi_priv.pwd_system->save_signature);
+ }
/* Authentication structures */
list_for_each_entry_safe(pos, n, &tlmi_priv.authentication_kset->list, entry)
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index b528a1e67dd3..1661f070c571 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -38,6 +38,7 @@
#include <linux/backlight.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -67,6 +68,7 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/string_choices.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/types.h>
@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t {
TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */
TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */
TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */
+ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */
TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */
TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */
@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct {
struct ibm_struct {
char *name;
+ acpi_device_class device_class;
int (*read) (struct seq_file *);
int (*write) (char *);
@@ -374,6 +378,8 @@ static struct {
u32 has_adaptive_kbd:1;
u32 kbd_lang:1;
u32 trackpoint_doubletap_enable:1;
+ u32 usbc_security_supported:1;
+ bool usbc_security_enabled;
struct quirk_entry *quirks;
} tp_features;
@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
}
ibm->acpi->device->driver_data = ibm;
- scnprintf(acpi_device_class(ibm->acpi->device),
- sizeof(acpi_device_class(ibm->acpi->device)),
- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name);
+ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s",
+ TPACPI_ACPI_EVENT_PREFIX, ibm->name);
status = acpi_install_notify_handler(*ibm->acpi->handle,
ibm->acpi->type, dispatch_acpi_notify, ibm);
@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
pr_err("unknown HKEY notification event %d\n", event);
/* forward it to userspace, maybe it knows how to handle it */
acpi_bus_generate_netlink_event(
- ibm->acpi->device->pnp.device_class,
+ ibm->device_class,
dev_name(&ibm->acpi->device->dev),
event, 0);
return;
@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
/* netlink events */
if (send_acpi_ev) {
acpi_bus_generate_netlink_event(
- ibm->acpi->device->pnp.device_class,
+ ibm->device_class,
dev_name(&ibm->acpi->device->dev),
event, hkey);
}
@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */
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_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 */
@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = {
.name = "hwdd",
};
+/*************************************************************************
+ * USB-C Security subdriver
+ *
+ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored.
+ * It always returns:
+ * bit 16 - USB-C security capability present on this SKU or not
+ * bit 0 - USB-C Security state (enable or disable)
+ *
+ * Hotkey
+ * ------
+ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event.
+ * The driver reads back the new state and notifies the sysfs attribute.
+ */
+
+/* USCS() return word bit layout */
+#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */
+#define USCS_STATUS_BIT BIT(0) /* current security state */
+
+/* Protects USCS() ACPI method calls in usbc_security_query() */
+static DEFINE_MUTEX(usbc_security_mutex);
+
+/**
+ * usbc_security_query - read current USB-C security state via USCS()
+ * @enabled: out - true when security is ON (data connections blocked)
+ *
+ * Returns:
+ * 0 success, @enabled contains the current state
+ * -EIO ACPI evaluation failed
+ * -ENODEV capability bit absent; feature not present on this SKU*
+ */
+static int usbc_security_query(bool *enabled)
+{
+ int status;
+
+ guard(mutex)(&usbc_security_mutex);
+ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0))
+ return -EIO;
+
+ if (!(status & USCS_CAP_BIT)) {
+ pr_debug("USCS cap bit absent (raw=0x%x)\n", status);
+ return -ENODEV;
+ }
+
+ *enabled = status & USCS_STATUS_BIT;
+ return 0;
+}
+
+/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */
+static ssize_t usb_c_security_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ str_enabled_disabled(tp_features.usbc_security_enabled));
+}
+
+static DEVICE_ATTR_RO(usb_c_security);
+
+static struct attribute *usbc_security_attributes[] = {
+ &dev_attr_usb_c_security.attr,
+ NULL,
+};
+
+static umode_t usbc_security_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return tp_features.usbc_security_supported ? attr->mode : 0;
+}
+
+static const struct attribute_group usbc_security_attr_group = {
+ .is_visible = usbc_security_attr_is_visible,
+ .attrs = usbc_security_attributes,
+};
+
+static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm)
+{
+ int err;
+
+ if (!acpi_has_method(hkey_handle, "USCS"))
+ return -ENODEV;
+
+ err = usbc_security_query(&tp_features.usbc_security_enabled);
+ if (err == -ENODEV)
+ return 0;
+ if (err)
+ return err;
+
+ tp_features.usbc_security_supported = true;
+ return 0;
+}
+
+/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */
+static bool tpacpi_usbc_security_hotkey(void)
+{
+ if (!tp_features.usbc_security_supported)
+ return false;
+
+ if (usbc_security_query(&tp_features.usbc_security_enabled))
+ return false;
+
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security");
+ return true;
+}
+
+static struct ibm_struct usbc_security_driver_data = {
+ .name = "usbc_security",
+};
+
/* --------------------------------------------------------------------- */
static struct attribute *tpacpi_driver_attributes[] = {
@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&dprc_attr_group,
&auxmac_attr_group,
&hwdd_attr_group,
+ &usbc_security_attr_group,
NULL,
};
@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
case TP_HKEY_EV_PROFILE_TOGGLE2:
platform_profile_cycle();
return true;
+ case TP_HKEY_EV_USB_C_SECURITY:
+ return tpacpi_usbc_security_hotkey();
}
return false;
@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = tpacpi_hwdd_init,
.data = &hwdd_driver_data,
},
+ {
+ .init = tpacpi_usbc_security_init,
+ .data = &usbc_security_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 000000000000..70d81f3af09e
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device (_UID "GMZN").
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
+ *
+ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
+ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
+ * The notify callback receives BKBD as an integer; no separate query needed.
+ *
+ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
+ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
+ * WmiDataId(2) uint32 Status = BKBD value
+ * Used on probe and resume to read initial state.
+ *
+ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI
+ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev
+ * and registers a notifier_block to receive those events, eliminating the
+ * need for shared global state or a mutex.
+ *
+ * SW_TABLET_MODE=1 is reported when the keyboard is detached;
+ * SW_TABLET_MODE=0 when docked in either position (keyboard present).
+ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dev_printk.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+/* BKBD encoding */
+#define BKBD_DETACHED 0
+
+/* LENOVO_FEATURE_STATUS_DATA feature selector */
+#define YB9_FEATURE_STATUS_ID 0x00060000u
+
+/*
+ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}.
+ * IDs is always 0x00060000; Status holds the BKBD value (0–3).
+ */
+struct lenovo_feature_status {
+ __le32 id;
+ __le32 status;
+} __packed;
+
+/* ------------------------------------------------------------------
+ * Notifier chain — event driver fires it, block driver listens
+ * ------------------------------------------------------------------ */
+
+static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head);
+
+static void devm_yb9_kbdock_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb);
+}
+
+static int devm_yb9_kbdock_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb);
+}
+
+/* ------------------------------------------------------------------
+ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
+ * (owns input_dev, sysfs, PM resume)
+ * ------------------------------------------------------------------ */
+
+struct yb9_kbdock_data {
+ struct wmi_device *wdev;
+ struct input_dev *input_dev;
+ struct notifier_block nb;
+ spinlock_t lock; /* protects input_report_switch + input_sync */
+};
+
+static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd)
+{
+ struct wmi_buffer out;
+ int ret;
+
+ ret = wmidev_query_block(d->wdev, 0, &out,
+ sizeof(struct lenovo_feature_status));
+ if (ret)
+ return ret;
+
+ struct lenovo_feature_status *fs __free(kfree) = out.data;
+
+ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID)
+ return -EIO;
+
+ *bkbd = le32_to_cpu(fs->status);
+ return 0;
+}
+
+static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd)
+{
+ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ spin_lock(&d->lock);
+ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet);
+ input_sync(d->input_dev);
+ spin_unlock(&d->lock);
+ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet);
+}
+
+static int yb9_kbdock_sync(struct yb9_kbdock_data *d)
+{
+ u32 bkbd;
+ int ret;
+
+ ret = yb9_kbdock_query(d, &bkbd);
+ if (ret)
+ return ret;
+
+ yb9_kbdock_report(d, bkbd);
+ return 0;
+}
+
+static int yb9_kbdock_nb_call(struct notifier_block *nb,
+ unsigned long bkbd, void *unused)
+{
+ struct yb9_kbdock_data *d =
+ container_of(nb, struct yb9_kbdock_data, nb);
+
+ yb9_kbdock_report(d, bkbd);
+ return NOTIFY_DONE;
+}
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct yb9_kbdock_data *d = dev_get_drvdata(dev);
+ u32 bkbd;
+ int ret;
+
+ ret = yb9_kbdock_query(d, &bkbd);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u\n", bkbd);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static const struct attribute * const yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+static int yb9_kbdock_resume(struct device *dev)
+{
+ struct yb9_kbdock_data *d = dev_get_drvdata(dev);
+
+ return yb9_kbdock_sync(d);
+}
+static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
+
+static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct yb9_kbdock_data *d;
+ struct input_dev *input_dev;
+ int ret;
+
+ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ d->wdev = wdev;
+ spin_lock_init(&d->lock);
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ ret = input_register_device(input_dev);
+ if (ret)
+ return ret;
+
+ d->input_dev = input_dev;
+ d->nb.notifier_call = yb9_kbdock_nb_call;
+
+ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&wdev->dev, d);
+ return yb9_kbdock_sync(d);
+}
+
+static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
+ { .guid_string = YB9_KBDOCK_QUERY_GUID },
+ { }
+};
+
+static struct wmi_driver yb9_kbdock_block_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
+ },
+ .id_table = yb9_kbdock_block_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_block_probe,
+};
+
+/* ------------------------------------------------------------------
+ * Event WMI driver — LENOVO_BTKBD_EVENT
+ * (fires the notifier chain on each WMI event)
+ * ------------------------------------------------------------------ */
+
+static void yb9_kbdock_notify_new(struct wmi_device *wdev,
+ const struct wmi_buffer *data)
+{
+ /*
+ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer
+ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
+ * Short-buffer guard is handled by .min_event_size below.
+ */
+ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data);
+
+ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL);
+}
+
+static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
+
+static struct wmi_driver yb9_kbdock_event_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock-event",
+ },
+ .id_table = yb9_kbdock_event_id_table,
+ .no_singleton = true,
+ .notify_new = yb9_kbdock_notify_new,
+ .min_event_size = sizeof(__le32),
+};
+
+/* ------------------------------------------------------------------
+ * Module init / exit
+ * ------------------------------------------------------------------ */
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+static int __init yb9_kbdock_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ ret = wmi_driver_register(&yb9_kbdock_event_driver);
+ if (ret)
+ return ret;
+
+ ret = wmi_driver_register(&yb9_kbdock_block_driver);
+ if (ret) {
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(yb9_kbdock_init);
+
+static void __exit yb9_kbdock_exit(void)
+{
+ wmi_driver_unregister(&yb9_kbdock_block_driver);
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+}
+module_exit(yb9_kbdock_exit);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c
index 1b73a55f1b89..df96107d2501 100644
--- a/drivers/platform/x86/lenovo/ymc.c
+++ b/drivers/platform/x86/lenovo/ymc.c
@@ -8,6 +8,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
@@ -20,10 +22,28 @@
#define LENOVO_YMC_QUERY_INSTANCE 0
#define LENOVO_YMC_QUERY_METHOD 0x01
+#define LENOVO_YMC_STATE_MASK GENMASK(7, 0)
+
static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
+static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
+ {
+ /*
+ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
+ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
+ * avoid userspace seeing two input nodes that both advertise
+ * the SW_TABLET_MODE capability.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
{
.matches = {
@@ -85,7 +105,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data)
"WMI event data is not an integer\n");
goto free_obj;
}
- code = obj->integer.value;
+
+ /* strip upper bits (e.g. 0x50000) on newer devices */
+ code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value);
if (!sparse_keymap_report_event(priv->input_dev, code, 1, true))
dev_warn(&wdev->dev, "Unknown key %d pressed\n", code);
@@ -101,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
struct input_dev *input_dev;
int err;
+ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
+ return -ENODEV;
+
if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
if (force)
dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
index a8f2f465ef3f..36ad1e269443 100644
--- a/drivers/platform/x86/lg-laptop.c
+++ b/drivers/platform/x86/lg-laptop.c
@@ -8,8 +8,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/compiler_attributes.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/dmi.h>
@@ -17,10 +20,12 @@
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/leds.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <linux/types.h>
+#include <linux/unaligned.h>
#include <acpi/battery.h>
@@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
#define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8
#define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8
-#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
-#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
-#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
-#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"
-#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
-#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
-#define WMI_EVENT_GUID WMI_EVENT_GUID0
+#define LG_NOTIFY_TABLET_MODE_OFF 0x50
+#define LG_NOTIFY_TABLET_MODE_ON 0x51
+#define LG_NOTIFY_HOTKEY 0x80
+#define LG_NOTIFY_THERMAL 0x81
+#define LG_NOTIFY_MISC 0x82
+
+#define LG_OREP_READ_EC 0
+#define LG_OREP_WRITE_EC 1
+#define LG_OREP_DEBUG 2
+#define LG_OREP_UPDATE_SYSTEM_STATE 3
+#define LG_OREP_INTERCEPT_WMI_EVENTS 4
+#define LG_OREP_WAKE_ON_LAN 6
#define SB_GGOV_METHOD "\\_SB.GGOV"
#define GOV_TLED 0x2020008
@@ -78,42 +88,95 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
#define WMBB_USB_CHARGE 0x10B
#define WMBB_BATT_LIMIT 0x10C
+#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0)
+#define KBD_LED_BRIGHTNESS_OFF 0x0
+#define KBD_LED_BRIGHTNESS_HALF 0x2
+#define KBD_LED_BRIGHTNESS_FULL 0x4
+#define KBD_LED_MODE_MASK GENMASK(6, 5)
+#define KBD_LED_MODE_OFF 0x0
+#define KBD_LED_MODE_ON 0x1
+#define KBD_LED_STATUS BIT(7)
+#define KBD_LED_MAGIC_MASK GENMASK(15, 8)
+/* Exact purpose is unknown, maybe some sort of brightness limit? */
+#define KBD_LED_MAGIC 0x05
+
#define FAN_MODE_LOWER GENMASK(1, 0)
#define FAN_MODE_UPPER GENMASK(5, 4)
#define PLATFORM_NAME "lg-laptop"
-MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
-MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);
-MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);
-MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);
-MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);
-MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);
+struct lg_wmab_buffer_result {
+ __le32 value;
+ __le32 status;
+} __packed;
static struct platform_device *pf_device;
-static struct input_dev *wmi_input_dev;
-
-static u32 inited;
-#define INIT_INPUT_WMI_0 0x01
-#define INIT_INPUT_WMI_2 0x02
-#define INIT_INPUT_ACPI 0x04
-#define INIT_SPARSE_KEYMAP 0x80
static int battery_limit_use_wmbb;
+static bool kbd_backlight_available;
static struct led_classdev kbd_backlight;
static enum led_brightness get_kbd_backlight_level(struct device *dev);
static const struct key_entry wmi_keymap[] = {
- {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */
- {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */
- {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */
- {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing
- * this key both sends an event and
- * changes backlight level.
- */
+ /* Placeholder value send by multiple hotkeys handled by ACPI */
+ { KE_IGNORE, 0x0, { KEY_UNKNOWN }},
+ /* LG control panel */
+ { KE_KEY, 0x70, { KEY_F15 }},
+ /* Touchpad toggle */
+ { KE_KEY, 0x74, { KEY_F21 }},
+ /* Mute Audio, already handled by ACPI */
+ { KE_IGNORE, 0x78, { KEY_MUTE }},
+ /* Read mode */
+ { KE_KEY, 0xf020000, { KEY_F14 }},
+ /* Open settings */
+ { KE_KEY, 0xf070002, { KEY_CONFIG }},
+ /* Keyboard backlight - pressing this key both sends an event and changes backlight level */
+ { KE_KEY, 0x10000000, { KEY_F16 }},
+ /* Hotkey combination pressed */
+ { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }},
+ /* Hotkey combination released */
+ { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }},
+ /* Change fan mode */
+ { KE_KEY, 0x30010051, { KEY_PERFORMANCE }},
+ /* Disable camera */
+ { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }},
+ /* Mute microphone */
+ { KE_KEY, 0x40020001, { KEY_MICMUTE }},
+ /* Fn-Lock */
+ { KE_KEY, 0x40030001, { KEY_FN_ESC }},
{KE_END, 0}
};
+static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value,
+ unsigned long long *result)
+{
+ union acpi_object objs[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = command,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = value,
+ },
+ }
+ };
+ struct acpi_object_list args = {
+ .count = ARRAY_SIZE(objs),
+ .pointer = objs,
+ };
+ acpi_status status;
+
+ status = acpi_evaluate_integer(handle, "OREP", &args, result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+
static int ggov(u32 arg0)
{
union acpi_object args[1];
@@ -154,30 +217,97 @@ static int ggov(u32 arg0)
return res;
}
-static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2)
+static int lg_wmab_get(struct device *dev, u32 method, u32 *result)
{
- union acpi_object args[3];
- acpi_status status;
- struct acpi_object_list arg;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object in[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = method,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = WM_GET,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0,
+ },
+ },
+ };
+ struct lg_wmab_buffer_result *buffer_result;
+ struct acpi_object_list input = {
+ .count = ARRAY_SIZE(in),
+ .pointer = in,
+ };
+ acpi_status status;
- args[0].type = ACPI_TYPE_INTEGER;
- args[0].integer.value = method;
- args[1].type = ACPI_TYPE_INTEGER;
- args[1].integer.value = arg1;
- args[2].type = ACPI_TYPE_INTEGER;
- args[2].integer.value = arg2;
+ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer);
+ if (ACPI_FAILURE(status))
+ return -EIO;
- arg.count = 3;
- arg.pointer = args;
+ union acpi_object *obj __free(kfree) = buffer.pointer;
- status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer);
- if (ACPI_FAILURE(status)) {
- dev_err(dev, "WMAB: call failed.\n");
- return NULL;
+ if (!obj)
+ return -ENODATA;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ *result = obj->integer.value;
+ return 0;
+ case ACPI_TYPE_BUFFER:
+ if (obj->buffer.length != sizeof(*buffer_result))
+ return -EPROTO;
+
+ buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer;
+ if (get_unaligned_le32(&buffer_result->status))
+ return -EIO;
+
+ *result = get_unaligned_le32(&buffer_result->value);
+ return 0;
+ default:
+ return -EPROTO;
}
+}
+
+static int lg_wmab_set(struct device *dev, u32 method, u32 arg)
+{
+ union acpi_object in[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = method,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = WM_SET,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = arg,
+ },
+ },
+ };
+ struct acpi_object_list input = {
+ .count = ARRAY_SIZE(in),
+ .pointer = in,
+ };
+ acpi_status status;
+
+ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL);
+ if (ACPI_FAILURE(status))
+ return -EIO;
- return buffer.pointer;
+ return 0;
}
static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2)
@@ -211,72 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u
return (union acpi_object *)buffer.pointer;
}
-static void wmi_notify(union acpi_object *obj, void *context)
-{
- long data = (long)context;
-
- pr_debug("event guid %li\n", data);
- if (!obj)
- return;
-
- if (obj->type == ACPI_TYPE_INTEGER) {
- int eventcode = obj->integer.value;
- struct key_entry *key;
-
- if (eventcode == 0x10000000) {
- led_classdev_notify_brightness_hw_changed(
- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent));
- } else {
- key = sparse_keymap_entry_from_scancode(
- wmi_input_dev, eventcode);
- if (key && key->type == KE_KEY)
- sparse_keymap_report_entry(wmi_input_dev,
- key, 1, true);
- }
- }
-
- pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,
- obj->integer.value);
-}
-
-static void wmi_input_setup(void)
-{
- acpi_status status;
-
- wmi_input_dev = input_allocate_device();
- if (wmi_input_dev) {
- wmi_input_dev->name = "LG WMI hotkeys";
- wmi_input_dev->phys = "wmi/input0";
- wmi_input_dev->id.bustype = BUS_HOST;
-
- if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) ||
- input_register_device(wmi_input_dev)) {
- pr_info("Cannot initialize input device");
- input_free_device(wmi_input_dev);
- return;
- }
-
- inited |= INIT_SPARSE_KEYMAP;
- status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,
- (void *)0);
- if (ACPI_SUCCESS(status))
- inited |= INIT_INPUT_WMI_0;
-
- status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,
- (void *)2);
- if (ACPI_SUCCESS(status))
- inited |= INIT_INPUT_WMI_2;
- } else {
- pr_info("Cannot allocate input device");
- }
-}
-
static ssize_t fan_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buffer, size_t count)
{
unsigned long value;
- union acpi_object *r;
int ret;
ret = kstrtoul(buffer, 10, &value);
@@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev,
if (value >= 3)
return -EINVAL;
- r = lg_wmab(dev, WM_FAN_MODE, WM_SET,
- FIELD_PREP(FAN_MODE_LOWER, value) |
- FIELD_PREP(FAN_MODE_UPPER, value));
- kfree(r);
+ ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) |
+ FIELD_PREP(FAN_MODE_UPPER, value));
+ if (ret < 0)
+ return ret;
return count;
}
@@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev,
static ssize_t fan_mode_show(struct device *dev,
struct device_attribute *attr, char *buffer)
{
- unsigned int mode;
- union acpi_object *r;
-
- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
+ u32 mode;
+ int ret;
- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value);
- kfree(r);
+ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode);
+ if (ret < 0)
+ return ret;
- return sysfs_emit(buffer, "%d\n", mode);
+ return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode));
}
static ssize_t usb_charge_store(struct device *dev,
@@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev,
const char *buffer, size_t count)
{
bool value;
- union acpi_object *r;
int ret;
ret = kstrtobool(buffer, &value);
if (ret)
return ret;
- r = lg_wmab(dev, WM_READER_MODE, WM_SET, value);
- if (!r)
- return -EIO;
+ ret = lg_wmab_set(dev, WM_READER_MODE, value);
+ if (ret < 0)
+ return ret;
- kfree(r);
return count;
}
static ssize_t reader_mode_show(struct device *dev,
struct device_attribute *attr, char *buffer)
{
- unsigned int status;
- union acpi_object *r;
-
- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- status = !!r->integer.value;
+ u32 status;
+ int ret;
- kfree(r);
+ ret = lg_wmab_get(dev, WM_READER_MODE, &status);
+ if (ret < 0)
+ return ret;
- return sysfs_emit(buffer, "%d\n", status);
+ return sysfs_emit(buffer, "%d\n", !!status);
}
static ssize_t fn_lock_store(struct device *dev,
@@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev,
const char *buffer, size_t count)
{
bool value;
- union acpi_object *r;
int ret;
ret = kstrtobool(buffer, &value);
if (ret)
return ret;
- r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value);
- if (!r)
- return -EIO;
+ ret = lg_wmab_set(dev, WM_FN_LOCK, value);
+ if (ret < 0)
+ return ret;
- kfree(r);
return count;
}
static ssize_t fn_lock_show(struct device *dev,
struct device_attribute *attr, char *buffer)
{
- unsigned int status;
- union acpi_object *r;
-
- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_BUFFER) {
- kfree(r);
- return -EIO;
- }
+ u32 status;
+ int ret;
- status = !!r->buffer.pointer[0];
- kfree(r);
+ ret = lg_wmab_get(dev, WM_FN_LOCK, &status);
+ if (ret < 0)
+ return ret;
- return sysfs_emit(buffer, "%d\n", status);
+ return sysfs_emit(buffer, "%d\n", !!status);
}
static ssize_t charge_control_end_threshold_store(struct device *dev,
@@ -453,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev,
if (value == 100 || value == 80) {
union acpi_object *r;
- if (battery_limit_use_wmbb)
+ if (battery_limit_use_wmbb) {
r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value);
- else
- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value);
- if (!r)
- return -EIO;
+ if (!r)
+ return -EIO;
+
+ kfree(r);
+ } else {
+ ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value);
+ if (ret < 0)
+ return ret;
+ }
- kfree(r);
return count;
}
@@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
- unsigned int status;
union acpi_object *r;
+ u32 status;
+ int ret;
if (battery_limit_use_wmbb) {
r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0);
@@ -485,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
}
status = r->buffer.pointer[0x10];
+ kfree(r);
} else {
- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- status = r->integer.value;
+ ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status);
+ if (ret < 0)
+ return ret;
}
- kfree(r);
+
if (status != 80 && status != 100)
status = 0;
@@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = {
static void tpad_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- union acpi_object *r;
-
- r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF);
- kfree(r);
+ lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF);
}
static enum led_brightness tpad_led_get(struct led_classdev *cdev)
@@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0);
static void kbd_backlight_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- u32 val;
- union acpi_object *r;
+ /* Must always be written */
+ u32 value = KBD_LED_STATUS;
+ u32 mode, bright;
- val = 0x22;
- if (brightness <= LED_OFF)
- val = 0;
- if (brightness >= LED_FULL)
- val = 0x24;
- r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val);
- kfree(r);
+ if (brightness <= LED_OFF) {
+ mode = KBD_LED_MODE_OFF;
+ bright = KBD_LED_BRIGHTNESS_OFF;
+ } else {
+ mode = KBD_LED_MODE_ON;
+ if (brightness >= LED_FULL)
+ bright = KBD_LED_BRIGHTNESS_FULL;
+ else
+ bright = KBD_LED_BRIGHTNESS_HALF;
+ }
+
+ value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright);
+ value |= FIELD_PREP(KBD_LED_MODE_MASK, mode);
+
+ lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value);
}
static enum led_brightness get_kbd_backlight_level(struct device *dev)
{
- union acpi_object *r;
- int val;
+ u32 value;
+ int ret;
- r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0);
+ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value);
+ if (ret < 0)
+ return LED_OFF;
- if (!r)
+ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC)
return LED_OFF;
- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {
- kfree(r);
+ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF)
return LED_OFF;
- }
- switch (r->buffer.pointer[0] & 0x27) {
- case 0x24:
- val = LED_FULL;
- break;
- case 0x22:
- val = LED_HALF;
- break;
+ switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) {
+ case KBD_LED_BRIGHTNESS_FULL:
+ return LED_FULL;
+ case KBD_LED_BRIGHTNESS_HALF:
+ return LED_HALF;
default:
- val = LED_OFF;
+ return LED_OFF;
}
-
- kfree(r);
-
- return val;
}
static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
@@ -629,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED);
-static void wmi_input_destroy(void)
+static struct platform_driver pf_driver = {
+ .driver = {
+ .name = PLATFORM_NAME,
+ }
+};
+
+static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data)
{
- if (inited & INIT_INPUT_WMI_2)
- wmi_remove_notify_handler(WMI_EVENT_GUID2);
+ union acpi_object objs[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = value,
+ },
+ }
+ };
+ struct acpi_object_list args = {
+ .count = ARRAY_SIZE(objs),
+ .pointer = objs,
+ };
+ unsigned long long result;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(handle, "_WED", &args, &result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
- if (inited & INIT_INPUT_WMI_0)
- wmi_remove_notify_handler(WMI_EVENT_GUID0);
+ if (result > U32_MAX)
+ return -EPROTO;
- if (inited & INIT_SPARSE_KEYMAP)
- input_unregister_device(wmi_input_dev);
+ *data = result;
- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);
+ return 0;
}
-static struct platform_driver pf_driver = {
- .driver = {
- .name = PLATFORM_NAME,
+static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data)
+{
+ unsigned int kbd_brightness;
+
+ switch (value) {
+ case LG_NOTIFY_HOTKEY:
+ sparse_keymap_report_event(input_dev, data, 1, true);
+ break;
+ case LG_NOTIFY_THERMAL:
+ /* Currently not supported */
+ break;
+ case LG_NOTIFY_MISC:
+ switch (data) {
+ case 0x10000000:
+ if (!kbd_backlight_available)
+ break;
+
+ kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent);
+ led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness);
+ break;
+ default:
+ sparse_keymap_report_event(input_dev, data, 1, true);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context)
+{
+ struct input_dev *input_dev = context;
+ u32 data;
+ int ret;
+
+ switch (value) {
+ case LG_NOTIFY_TABLET_MODE_OFF:
+ case LG_NOTIFY_TABLET_MODE_ON:
+ /* Already handled by intel-hid */
+ return;
+ case LG_NOTIFY_HOTKEY:
+ case LG_NOTIFY_THERMAL:
+ case LG_NOTIFY_MISC:
+ ret = lg_laptop_get_event_data(handle, value, &data);
+ if (ret < 0) {
+ dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data);
+
+ lg_laptop_handle_input_event(input_dev, value, data);
+ return;
+ default:
+ dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value);
}
};
+static void lg_laptop_remove_notify_handler(void *context)
+{
+ acpi_handle handle = context;
+
+ acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler);
+}
+
+static void lg_laptop_reenable_wmi_events(void *context)
+{
+ acpi_handle handle = context;
+ unsigned long long dummy;
+
+ lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy);
+}
+
+static int lg_laptop_input_init(struct device *dev, acpi_handle handle)
+{
+ struct input_dev *input_dev;
+ unsigned long long result;
+ acpi_status status;
+ int ret;
+
+ if (!acpi_has_method(handle, "_WED"))
+ return 0;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "LG WMI hotkeys";
+ input_dev->phys = "wmi/input0";
+ input_dev->id.bustype = BUS_HOST;
+ ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = input_register_device(input_dev);
+ if (ret < 0)
+ return ret;
+
+ status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler,
+ input_dev);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle);
+ if (ret < 0)
+ return ret;
+
+ if (acpi_has_method(handle, "OREP")) {
+ ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result);
+ if (ret < 0)
+ return ret;
+ if (result)
+ return -EIO;
+
+ ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address,
size_t size, u64 value)
{
@@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev)
if (year >= 2019)
battery_limit_use_wmbb = 1;
+ /* LEDs are optional */
+ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight);
+ if (ret < 0)
+ kbd_backlight_available = false;
+ else
+ kbd_backlight_available = true;
+
+ devm_led_classdev_register(&pdev->dev, &tpad_led);
+
+ ret = lg_laptop_input_init(&pdev->dev, device->handle);
+ if (ret < 0)
+ goto out_platform_device;
+
ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
if (ret)
goto out_platform_device;
- /* LEDs are optional */
- led_classdev_register(&pf_device->dev, &kbd_backlight);
- led_classdev_register(&pf_device->dev, &tpad_led);
-
- wmi_input_setup();
battery_hook_register(&battery_hook);
return 0;
@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev)
{
sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
- led_classdev_unregister(&tpad_led);
- led_classdev_unregister(&kbd_backlight);
-
battery_hook_unregister(&battery_hook);
- wmi_input_destroy();
platform_device_unregister(pf_device);
pf_device = NULL;
platform_driver_unregister(&pf_driver);
diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c
index dfe4532ebe56..566dfc73cb67 100644
--- a/drivers/platform/x86/msi-ec.c
+++ b/drivers/platform/x86/msi-ec.c
@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = {
static const char * const ALLOWED_FW_13[] __initconst = {
"1594EMS1.109", // MSI Prestige 16 Studio A13VE
+ "17L1EMS1.107", // MSI Katana GF76 11UEK
NULL
};
@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = {
},
};
+static const char * const ALLOWED_FW_14[] __initconst = {
+ "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
+ "182KIMS1.113", // Raider A18 HX A7VIG
+ NULL
+};
+
+static struct msi_ec_conf CONF14 __initdata = {
+ .allowed_fw = ALLOWED_FW_14,
+ .charge_control = {
+ .address = 0xd7,
+ .offset_start = 0x8a,
+ .offset_end = 0x80,
+ .range_min = 0x8a,
+ .range_max = 0xe4,
+ },
+ .webcam = {
+ .address = 0x2e,
+ .block_address = 0x2f,
+ .bit = 1,
+ },
+ .fn_win_swap = {
+ .address = 0xe8,
+ .bit = 4,
+ },
+ .cooler_boost = {
+ .address = 0x98,
+ .bit = 7,
+ },
+ .shift_mode = {
+ .address = 0xd2,
+ .modes = {
+ { SM_ECO_NAME, 0xc2 },
+ { SM_COMFORT_NAME, 0xc1 },
+ { SM_TURBO_NAME, 0xc4 },
+ MSI_EC_MODE_NULL
+ },
+ },
+ .super_battery = {
+ .address = 0xeb,
+ .mask = 0x0f,
+ },
+ .fan_mode = {
+ .address = 0xd4,
+ .modes = {
+ { FM_AUTO_NAME, 0x0d },
+ { FM_SILENT_NAME, 0x1d },
+ { FM_ADVANCED_NAME, 0x8d },
+ MSI_EC_MODE_NULL
+ },
+ },
+ .cpu = {
+ .rt_temp_address = 0x68,
+ .rt_fan_speed_address = 0x71,
+ .rt_fan_speed_base_min = 0x00,
+ .rt_fan_speed_base_max = 0x96,
+ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
+ .bs_fan_speed_base_min = 0x00,
+ .bs_fan_speed_base_max = 0x0f,
+ },
+ .gpu = {
+ .rt_temp_address = 0x80,
+ .rt_fan_speed_address = 0x89,
+ },
+ .leds = {
+ .micmute_led_address = 0x2c,
+ .mute_led_address = 0x2d,
+ .bit = 1,
+ },
+ .kbd_bl = {
+ .bl_mode_address = MSI_EC_ADDR_UNSUPP,
+ .bl_modes = { 0x00, 0x08 },
+ .max_mode = 1,
+ .bl_state_address = MSI_EC_ADDR_UNSUPP,
+ .state_base_value = 0x80,
+ .max_state = 3,
+ },
+};
+
static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF0,
&CONF1,
@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF11,
&CONF12,
&CONF13,
+ &CONF14,
NULL
};
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
index 4a7ac85c4db4..c9db750fe5ae 100644
--- a/drivers/platform/x86/msi-wmi.c
+++ b/drivers/platform/x86/msi-wmi.c
@@ -46,6 +46,12 @@ enum msi_scancodes {
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 */
+ CLAW_KEY_VOLUMEDOWN = 0x21,
+ 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,
+ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */
};
static struct key_entry msi_wmi_keymap[] = {
{ KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} },
@@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = {
{ KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} },
{ KE_KEY, WIND_KEY_ECO, {KEY_PROG2} },
+ /* These are MSI Claw keys, used for MSI M-Center in Windows */
+ { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} },
+
+ /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */
+ { KE_IGNORE, CLAW_KEY_QUICK_SHORT },
+ { KE_IGNORE, CLAW_KEY_QUICK_LONG },
+ { KE_IGNORE, CLAW_KEY_VOLUMEUP },
+ { KE_IGNORE, CLAW_KEY_VOLUMEDOWN },
+
{ KE_END, 0 }
};
@@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = {
static void msi_wmi_notify(union acpi_object *obj, void *context)
{
- struct key_entry *key;
+ struct key_entry *key = NULL;
+ int eventcode = 0;
+
+ if (!obj)
+ return;
- if (obj && obj->type == ACPI_TYPE_INTEGER) {
- int eventcode = obj->integer.value;
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ eventcode = obj->integer.value;
pr_debug("Eventcode: 0x%x\n", eventcode);
- key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
- eventcode);
- if (!key) {
- pr_info("Unknown key pressed - %x\n", eventcode);
+ break;
+ case ACPI_TYPE_BUFFER:
+ /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */
+ if (obj->buffer.length < 2)
return;
- }
- if (event_wmi->quirk_last_pressed) {
- ktime_t cur = ktime_get_real();
- ktime_t diff = ktime_sub(cur, last_pressed);
- /* Ignore event if any event happened in a 50 ms
- timeframe -> Key press may result in 10-20 GPEs */
- if (ktime_to_us(diff) < 1000 * 50) {
- pr_debug("Suppressed key event 0x%X - "
- "Last press was %lld us ago\n",
- key->code, ktime_to_us(diff));
- return;
- }
- last_pressed = cur;
- }
+ /* pointer[0] is key ID, pointer[1] is active state. We don't get release
+ * events, so ignore the active state and treat as autorelease.
+ */
+ eventcode = obj->buffer.pointer[0];
+ pr_debug("Eventcode: 0x%x\n", eventcode);
+ break;
+ default:
+ pr_info("Unknown event received\n");
+ return;
+ }
+
+ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode);
- if (key->type == KE_KEY &&
- /* Brightness is served via acpi video driver */
- (backlight ||
- (key->code != MSI_KEY_BRIGHTNESSUP &&
- key->code != MSI_KEY_BRIGHTNESSDOWN))) {
- pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
- key->code, key->keycode);
- sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
- true);
+ if (!key) {
+ pr_info("Unknown key pressed - 0x%x\n", eventcode);
+ return;
+ }
+
+ if (event_wmi->quirk_last_pressed) {
+ ktime_t cur = ktime_get_real();
+ ktime_t diff = ktime_sub(cur, last_pressed);
+ /* Ignore event if any event happened in a 50 ms
+ * timeframe -> Key press may result in 10-20 GPEs
+ */
+ if (ktime_to_us(diff) < 1000 * 50) {
+ pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n",
+ key->code, ktime_to_us(diff));
+ return;
}
- } else
- pr_info("Unknown event received\n");
+ last_pressed = cur;
+ }
+
+ /* Brightness is served via acpi video driver */
+ 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);
+ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true);
}
static int __init msi_wmi_backlight_setup(void)
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
index 99c0dfcf393b..34bb17fca148 100644
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)oxp_x1,
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
{},
};
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 719add753cb3..19d194ff37ca 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -159,8 +159,6 @@ MODULE_LICENSE("GPL");
#define ECO_MODE_ON 0x80
#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
-#define ACPI_PCC_DEVICE_NAME "Hotkey"
-#define ACPI_PCC_CLASS "pcc"
#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
@@ -360,7 +358,16 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
} else
pr_err("Invalid HKEY.SINF data\n");
}
- pcc->sinf[hkey->package.count] = -1;
+ /*
+ * pcc->sinf[] has pcc->num_sifr elements (valid indices
+ * 0..num_sifr-1). On DSDTs where SINF's package count equals
+ * num_sifr exactly -- the off-by-one case probe()'s num_sifr++
+ * already allocates a spare element for -- there is no room left
+ * for this trailing sentinel; nothing reads it back, so just skip
+ * the write rather than running one element past the flex array.
+ */
+ if (hkey->package.count < pcc->num_sifr)
+ pcc->sinf[hkey->package.count] = -1;
end:
kfree(buffer.pointer);
@@ -1017,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
pcc->device = device;
pcc->handle = device->handle;
device->driver_data = pcc;
- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
- strscpy(acpi_device_class(device), ACPI_PCC_CLASS);
result = acpi_pcc_init_input(pcc);
if (result) {
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
index 58898630eda6..cc82ef502002 100644
--- a/drivers/platform/x86/redmi-wmi.c
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = {
{KE_KEY, 0x00011801, {KEY_ASSISTANT}},
{KE_KEY, 0x00011901, {KEY_ASSISTANT}},
- /* Keyboard backlight */
- {KE_IGNORE, 0x00000501, {}},
- {KE_IGNORE, 0x00800501, {}},
- {KE_IGNORE, 0x00050501, {}},
- {KE_IGNORE, 0x000a0501, {}},
+ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */
+ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}},
/* Xiaomi G Command Center */
{KE_KEY, 0x00010a01, {KEY_VENDOR}},
- /* OEM preset power mode */
- {KE_IGNORE, 0x00011601, {}},
- {KE_IGNORE, 0x00021601, {}},
- {KE_IGNORE, 0x00031601, {}},
- {KE_IGNORE, 0x00041601, {}},
+ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */
+ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}},
- /* Fn Lock state */
- {KE_IGNORE, 0x00000701, {}},
- {KE_IGNORE, 0x00010701, {}},
+ /* Fn Lock state: 1=on 0=off */
+ {KE_KEY, 0x00000701, {KEY_FN_ESC}},
+ {KE_KEY, 0x00010701, {KEY_FN_ESC}},
/* Fn+`/1/2/3/4 */
{KE_KEY, 0x00011101, {KEY_F13}},
diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c
index 6382af0b106c..99f72b204a51 100644
--- a/drivers/platform/x86/samsung-galaxybook.c
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = {
{ "SAM0428" },
{ "SAM0429" },
{ "SAM0430" },
+ { "SAMB430" },
{}
};
MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index 67370967df6f..0bf4d3054e37 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data)
ev_type = HOTKEY;
sony_laptop_report_input_event(real_ev);
}
- acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class,
+ acpi_bus_generate_netlink_event("sony/hotkey",
dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev);
}
@@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev)
return -ENODEV;
sony_nc_acpi_device = device;
- strscpy(acpi_device_class(device), "sony/hotkey");
-
sony_nc_acpi_handle = device->handle;
/* read device status */
@@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev)
return -ENODEV;
spic_dev.acpi_dev = device;
- strscpy(acpi_device_class(device), "sony/hotkey");
sony_pic_detect_device_type(&spic_dev);
mutex_init(&spic_dev.lock);
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
index e09d7f8ce45f..0e842c55dcc5 100644
--- a/drivers/platform/x86/topstar-laptop.c
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, topstar);
- strscpy(acpi_device_name(device), "Topstar TPSACPI");
- strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
topstar->device = device;
err = topstar_acpi_init(topstar);
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index 7cecb3a70b9c..a0b8060836d0 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work)
LED_FULL : LED_OFF);
/* Emulate the keyboard backlight event */
- acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class,
- dev_name(&toshiba_acpi->acpi_dev->dev),
+ acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev),
0x92, 0);
}
@@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data)
break;
}
- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
- dev_name(&acpi_dev->dev),
+ acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev),
event, (event == 0x80) ?
dev->last_key_event : 0);
}
diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c
index e00abba58c7c..bf199fba4999 100644
--- a/drivers/platform/x86/toshiba_bluetooth.c
+++ b/drivers/platform/x86/toshiba_bluetooth.c
@@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, bt_dev);
result = toshiba_bluetooth_sync_status(bt_dev);
- if (result) {
- kfree(bt_dev);
- return result;
- }
+ if (result)
+ goto err_free_bt_dev;
bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
&pdev->dev,
diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c
index 8d12241924df..c6633b74029f 100644
--- a/drivers/platform/x86/toshiba_haps.c
+++ b/drivers/platform/x86/toshiba_haps.c
@@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data)
pr_debug("Received event: 0x%x\n", event);
- acpi_bus_generate_netlink_event(device->pnp.device_class,
- dev_name(&device->dev),
- event, 0);
+ acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0);
}
static void toshiba_haps_remove(struct platform_device *pdev)
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index ab063ead45b9..7a2eeaec4c96 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -95,6 +95,9 @@
#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
+#define EC_ADDR_SCREEN_STATUS 0x0466
+#define SCREEN_SUSPENDED BIT(6)
+
#define EC_ADDR_SECOND_FAN_RPM_1 0x046C
#define EC_ADDR_SECOND_FAN_RPM_2 0x046D
@@ -109,9 +112,35 @@
#define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7
+#define EC_ADDR_OEM_9 0x0726
+#define AC_AUTO_BOOT_ENABLE BIT(3)
+
#define EC_ADDR_PROJECT_ID 0x0740
+#define PROJECT_ID_NONE 0x00
+#define PROJECT_ID_GI 0x01
+#define PROJECT_ID_GJ 0x02
+#define PROJECT_ID_GK 0x03
+#define PROJECT_ID_GICN 0x04
+#define PROJECT_ID_GJCN 0x05
+#define PROJECT_ID_GK5CN_X 0x06
+#define PROJECT_ID_GK7CN_S 0x07
+#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08
+#define PROJECT_ID_PF 0x09
+#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A
+#define PROJECT_ID_IDP 0x0B
+#define PROJECT_ID_IDY_6Y 0x0C
+#define PROJECT_ID_IDY_7Y 0x0D
+#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E
+#define PROJECT_ID_CML_GAMING 0x0F
+#define PROJECT_ID_GK7NXXR 0x10
+#define PROJECT_ID_GM5MU1Y 0x11
#define PROJECT_ID_PH4TRX1 0x12
+#define PROJECT_ID_PH4TUX1 0x13
+#define PROJECT_ID_PH4TQX1 0x14
#define PROJECT_ID_PH6TRX1 0x15
+#define PROJECT_ID_PH6TQXX 0x16
+#define PROJECT_ID_PHXAXXX 0x17
+#define PROJECT_ID_PHXPXXX 0x18
#define EC_ADDR_AP_OEM 0x0741
#define ENABLE_MANUAL_CTRL BIT(0)
@@ -214,6 +243,7 @@
#define FAN_TABLE_OFFICE_MODE BIT(2)
#define FAN_V3 BIT(3)
#define DEFAULT_MODE BIT(4)
+#define ENABLE_CHINA_MODE BIT(6)
#define EC_ADDR_PL1_SETTING 0x0783
@@ -225,11 +255,12 @@
#define FAN_CURVE_LENGTH 5
#define EC_ADDR_KBD_STATUS 0x078C
-#define KBD_WHITE_ONLY BIT(0) // ~single color
-#define KBD_SINGLE_COLOR_OFF BIT(1)
+/* Unreliable on some models, use the device descriptor instead. */
+#define KBD_WHITE_ONLY BIT(0)
+#define KBD_POWER_OFF BIT(1)
#define KBD_TURBO_LEVEL_MASK GENMASK(3, 2)
#define KBD_APPLY BIT(4)
-#define KBD_BRIGHTNESS GENMASK(7, 5)
+#define KBD_BRIGHTNESS_MASK GENMASK(7, 5)
#define EC_ADDR_FAN_CTRL 0x078E
#define FAN3P5 BIT(1)
@@ -318,7 +349,9 @@
#define FAN_TABLE_LENGTH 16
#define LED_CHANNELS 3
-#define LED_MAX_BRIGHTNESS 200
+
+#define KBD_LED_CHANNELS 3
+#define KBD_LED_MAX_INTENSITY 50
#define UNIWILL_FEATURE_FN_LOCK BIT(0)
#define UNIWILL_FEATURE_SUPER_KEY BIT(1)
@@ -333,6 +366,9 @@
#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9)
#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10)
#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11)
+#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12)
+#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13)
+#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14)
enum usb_c_power_priority_options {
USB_C_POWER_PRIORITY_CHARGING = 0,
@@ -344,6 +380,7 @@ struct uniwill_data {
acpi_handle handle;
struct regmap *regmap;
unsigned int features;
+ u8 project_id;
struct acpi_battery_hook hook;
struct mutex battery_lock; /* Protects the list of currently registered batteries */
union {
@@ -357,11 +394,25 @@ struct uniwill_data {
bool last_fn_lock_state;
bool last_super_key_enable_state;
bool last_touchpad_toggle_enable_state;
+ bool last_usb_powershare_high_state;
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;
struct led_classdev_mc led_mc_cdev;
struct mc_subled led_mc_subled_info[LED_CHANNELS];
+ bool kbd_led_single_color;
+ u8 kbd_led_max_brightness;
+ unsigned int last_kbd_status;
+ union {
+ struct {
+ /* Protects writes to the RGB keyboard backlight registers */
+ struct mutex kbd_rgb_led_lock;
+ struct led_classdev_mc kbd_led_mc_cdev;
+ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS];
+ };
+ struct led_classdev kbd_led_cdev;
+ };
struct mutex input_lock; /* Protects input sequence during notify */
struct input_dev *input_device;
struct notifier_block nb;
@@ -376,6 +427,9 @@ struct uniwill_battery_entry {
struct uniwill_device_descriptor {
unsigned int features;
+ bool kbd_led_single_color;
+ u8 kbd_led_max_brightness;
+ u8 lightbar_max_brightness;
/* Executed during driver probing */
int (*probe)(struct uniwill_data *data);
};
@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = {
{ KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }},
{ KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }},
+ /* Reported when the EC changed the keyboard backlight brightness */
+ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }},
+
/* Reported when the user wants to toggle the microphone mute status */
{ KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }},
@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the brightness of the keyboard */
{ KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }},
/* FIXME: find out the exact meaning of those events */
{ KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }},
@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the benchmark mode status */
{ KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }},
+ /* Reported when the screen is enabled/disabled during resume/suspend */
+ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }},
+
/* Reported when the user wants to toggle the webcam */
{ KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = {
static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
+ case EC_ADDR_OEM_9:
case EC_ADDR_AP_OEM:
case EC_ADDR_LIGHTBAR_AC_CTRL:
case EC_ADDR_LIGHTBAR_AC_RED:
@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_AC_BLUE:
case EC_ADDR_BIOS_OEM:
case EC_ADDR_TRIGGER:
+ case EC_ADDR_RGB_RED:
+ case EC_ADDR_RGB_GREEN:
+ case EC_ADDR_RGB_BLUE:
+ case EC_ADDR_BIOS_OEM_2:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_LIGHTBAR_BAT_CTRL:
@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_SECOND_FAN_RPM_1:
case EC_ADDR_SECOND_FAN_RPM_2:
case EC_ADDR_BAT_ALERT:
+ case EC_ADDR_OEM_9:
case EC_ADDR_PROJECT_ID:
case EC_ADDR_AP_OEM:
case EC_ADDR_LIGHTBAR_AC_CTRL:
@@ -583,8 +645,14 @@ 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_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_RGB_RED:
+ case EC_ADDR_RGB_GREEN:
+ case EC_ADDR_RGB_BLUE:
+ case EC_ADDR_BIOS_OEM_2:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_LIGHTBAR_BAT_CTRL:
@@ -616,8 +684,10 @@ 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_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_USB_C_POWER_PRIORITY:
@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data)
return 0;
}
+static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ regval = AC_AUTO_BOOT_ENABLE;
+ else
+ regval = 0;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, &regval);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE));
+}
+
+static DEVICE_ATTR_RW(ac_auto_boot);
+
+static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status)
+{
+ unsigned int value;
+
+ if (status)
+ value = TRIGGER_USB_CHARGING;
+ else
+ value = 0;
+
+ /*
+ * Normaly this RMW-sequence could also trigger the super key toggle,
+ * but the EC seems to take care that those bits are always read as 0.
+ */
+ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value);
+}
+
+static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_write_usb_powershare_high(data, enable);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value);
+ if (ret < 0)
+ return ret;
+
+ *status = !!(value & TRIGGER_USB_CHARGING);
+
+ return 0;
+}
+
+static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ bool status;
+ int ret;
+
+ ret = uniwill_read_usb_powershare_high(data, &status);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", status);
+}
+
+static DEVICE_ATTR_RW(usb_powershare_high);
+
static struct attribute *uniwill_attrs[] = {
/* Keyboard-related */
&dev_attr_fn_lock.attr,
@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = {
/* Power-management-related */
&dev_attr_ctgp_offset.attr,
&dev_attr_usb_c_power_priority.attr,
+ &dev_attr_ac_auto_boot.attr,
+ &dev_attr_usb_powershare_high.attr,
NULL
};
@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a
return attr->mode;
}
+ if (attr == &dev_attr_ac_auto_boot.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_usb_powershare_high.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return attr->mode;
+ }
+
return 0;
}
@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br
for (int i = 0; i < LED_CHANNELS; i++) {
/* Prevent the brightness values from overflowing */
- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness);
+ 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);
if (ret < 0)
return ret;
@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data)
return ret;
data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS;
+ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness;
data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT;
data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set;
if (value & LIGHTBAR_S0_OFF)
data->led_mc_cdev.led_cdev.brightness = 0;
else
- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS;
+ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness;
for (int i = 0; i < LED_CHANNELS; i++) {
data->led_mc_subled_info[i].color_index = color_indices[i];
@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data)
* Make sure that the initial intensity value is not greater than
* the maximum brightness.
*/
- value = min(LED_MAX_BRIGHTNESS, value);
+ value = min(data->lightbar_max_brightness, value);
ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
if (ret < 0)
return ret;
@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data)
&init_data);
}
+static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness)
+{
+ struct led_classdev *led_cdev;
+ int ret;
+
+ if (data->kbd_led_single_color)
+ led_cdev = &data->kbd_led_cdev;
+ else
+ led_cdev = &data->kbd_led_mc_cdev.led_cdev;
+
+ guard(mutex)(&led_cdev->led_access);
+
+ /* Sync the LED brightness with the actual hardware state */
+ ret = led_update_brightness(led_cdev);
+ if (ret < 0)
+ return ret;
+
+ led_classdev_notify_brightness_hw_changed(led_cdev, brightness);
+
+ return 0;
+}
+
+#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF)
+
+static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness)
+{
+ /* KBD_POWER_OFF is always implicitly cleared */
+ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY;
+
+ /* We must ensure that the "apply" bit is always written */
+ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval);
+}
+
+static int uniwill_kbd_led_read_brightness(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness);
+}
+
+static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev);
+
+ return uniwill_kbd_led_write_brightness(data, brightness);
+}
+
+static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev)
+{
+ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev);
+
+ return uniwill_kbd_led_read_brightness(data);
+}
+
+static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = {
+ EC_ADDR_RGB_RED,
+ EC_ADDR_RGB_GREEN,
+ EC_ADDR_RGB_BLUE,
+};
+
+static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev);
+ unsigned int min_intensity = 0;
+ unsigned int regval;
+ int ret;
+
+ guard(mutex)(&data->kbd_rgb_led_lock);
+
+ /*
+ * The EC interprets a RGB value of 0x000000 as a command to restore
+ * the device-specfic 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 &&
+ data->kbd_led_mc_subled_info[1].intensity == 0 &&
+ data->kbd_led_mc_subled_info[2].intensity == 0)
+ min_intensity = 1;
+
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity);
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_kbd_led_write_brightness(data, brightness);
+}
+
+static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev);
+
+ return uniwill_kbd_led_read_brightness(data);
+}
+
+static int uniwill_white_kbd_led_init(struct uniwill_data *data)
+{
+ struct led_init_data init_data = {
+ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT,
+ .devicename = DRIVER_NAME,
+ .devname_mandatory = true,
+ };
+
+ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness;
+ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE;
+ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT;
+ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set;
+ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get;
+
+ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data);
+}
+
+static int uniwill_rgb_kbd_led_init(struct uniwill_data *data)
+{
+ unsigned int color_indices[KBD_LED_CHANNELS] = {
+ LED_COLOR_ID_RED,
+ LED_COLOR_ID_GREEN,
+ LED_COLOR_ID_BLUE,
+ };
+ struct led_init_data init_data = {
+ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT,
+ .devicename = DRIVER_NAME,
+ .devname_mandatory = true,
+ };
+ bool intensity_all_zeros = true;
+ bool needs_trigger = false;
+ unsigned int regval;
+ int ret;
+
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ data->kbd_led_mc_subled_info[i].color_index = color_indices[i];
+
+ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], &regval);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Make sure that the initial intensity value is not greater than
+ * the maximum intensity.
+ */
+ if (regval > KBD_LED_MAX_INTENSITY) {
+ regval = KBD_LED_MAX_INTENSITY;
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval);
+ if (ret < 0)
+ return ret;
+
+ needs_trigger = true;
+ }
+
+ if (regval)
+ intensity_all_zeros = false;
+
+ data->kbd_led_mc_subled_info[i].intensity = regval;
+ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY;
+ data->kbd_led_mc_subled_info[i].channel = i;
+ }
+
+ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */
+ if (intensity_all_zeros) {
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ data->kbd_led_mc_subled_info[i].intensity = 1;
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ needs_trigger = true;
+ }
+
+ if (needs_trigger) {
+ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR,
+ RGB_APPLY_COLOR);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock);
+ if (ret < 0)
+ 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.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;
+ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info;
+ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS;
+
+ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev,
+ &init_data);
+}
+
+static int uniwill_kbd_led_init(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &regval);
+ if (ret < 0)
+ return ret;
+
+ if (!(regval & CHINA_MODE)) {
+ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval |= KBD_APPLY;
+ regval &= ~KBD_POWER_OFF;
+ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval);
+ if (ret < 0)
+ return ret;
+
+ if (data->kbd_led_single_color)
+ return uniwill_white_kbd_led_init(data);
+
+ return uniwill_rgb_kbd_led_init(data);
+}
+
static unsigned int uniwill_sanitize_battery_threshold(unsigned int value)
{
/* 0 means "charging threshold not active" */
@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
sysfs_notify(&data->dev->kobj, NULL, "fn_lock");
return NOTIFY_OK;
+ case UNIWILL_OSD_KB_LED_LEVEL0:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 0));
+ case UNIWILL_OSD_KB_LED_LEVEL1:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 1));
+ case UNIWILL_OSD_KB_LED_LEVEL2:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 2));
+ case UNIWILL_OSD_KB_LED_LEVEL3:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 3));
+ case UNIWILL_OSD_KB_LED_LEVEL4:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 4));
default:
mutex_lock(&data->input_lock);
sparse_keymap_report_event(data->input_device, action, 1, true);
@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data)
if (ret < 0)
return ret;
+ data->project_id = value;
dev_dbg(data->dev, "Project ID: %u\n", value);
ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL);
@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev)
return ret;
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;
/*
* Some devices might need to perform some device-specific initialization steps
@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ ret = uniwill_kbd_led_init(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_hwmon_init(data);
if (ret < 0)
return ret;
@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data)
return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
}
+static int uniwill_suspend_kbd_led(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Save the current keyboard backlight settings in order to restore them
+ * during resume. We cannot use the regmap code for that since this register
+ * needs to be declared as volatile because the brightness can be changed
+ * by the EC.
+ */
+ data->last_kbd_status = regval;
+ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, &regval, 0);
+ regval |= KBD_APPLY | KBD_POWER_OFF;
+
+ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval);
+}
+
+static int uniwill_suspend_usb_powershare(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return 0;
+
+ /*
+ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it
+ * ourselves.
+ */
+ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state);
+}
+
static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_suspend_kbd_led(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_suspend_usb_powershare(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_suspend_nvidia_ctgp(data);
if (ret < 0)
return ret;
@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data)
return 0;
}
+static int uniwill_resume_kbd_led(struct uniwill_data *data)
+{
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY);
+ if (ret < 0)
+ return ret;
+
+ if (data->kbd_led_single_color)
+ return 0;
+
+ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR);
+}
+
+static int uniwill_resume_usb_powershare(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return 0;
+
+ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state);
+}
+
static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_resume_kbd_led(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_usb_powershare(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_resume_nvidia_ctgp(data);
if (ret < 0)
return ret;
@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = {
.shutdown = uniwill_shutdown,
};
+static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
+};
+
static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = {
.features = UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
+ .lightbar_max_brightness = 36,
};
static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
+ .lightbar_max_brightness = 200,
};
/*
@@ -2198,16 +2752,29 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini
UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
};
-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = {
+static struct uniwill_device_descriptor tux_featureset_3_nvidia_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_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
};
-static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = {
+static struct uniwill_device_descriptor tux_featureset_4_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_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+};
+
+static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
@@ -2215,7 +2782,9 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
- UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
};
static int phxtxx1_probe(struct uniwill_data *data)
@@ -2277,8 +2846,38 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = {
UNIWILL_FEATURE_PRIMARY_FAN,
};
+static struct uniwill_device_descriptor x4sp4nal_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_KEYBOARD_BACKLIGHT |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+ .kbd_led_single_color = true,
+ .kbd_led_max_brightness = 2,
+};
+
static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
{
+ .ident = "AiStone X4SP4NAL",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"),
+ },
+ .driver_data = &x4sp4nal_descriptor,
+ },
+ {
+ .ident = "MACHENIKE L16 Pro",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"),
+ },
+ .driver_data = &machenike_l16p_descriptor,
+ },
+ {
.ident = "XMG FUSION 15 (L19)",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
@@ -2311,6 +2910,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
.driver_data = &lapqc71a_lapqc71b_descriptor,
},
{
+ .ident = "Avell A60 MUV",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
+ {
.ident = "Intel NUC x15",
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
@@ -2388,7 +2994,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_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
@@ -2396,7 +3002,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_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -2404,7 +3010,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_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -2412,7 +3018,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_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -2420,7 +3026,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_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -2428,7 +3034,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -2436,7 +3042,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -2444,7 +3050,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -2452,7 +3058,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -2460,7 +3066,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -2620,7 +3226,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
@@ -2628,7 +3234,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -2636,7 +3242,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -2644,7 +3250,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -2652,7 +3258,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -2660,7 +3266,7 @@ 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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -2668,7 +3274,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -2676,7 +3282,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_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void)
if (force) {
/* Assume that the device supports all features except the charge limit */
device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT;
+ /* Some models only have a (white) single color keyboard backlight */
+ 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;
pr_warn("Enabling potentially unsupported features\n");
}
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
index fb1910c0f741..b25b2f31211c 100644
--- a/drivers/platform/x86/uniwill/uniwill-wmi.h
+++ b/drivers/platform/x86/uniwill/uniwill-wmi.h
@@ -113,6 +113,8 @@
#define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0
+#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC
+
#define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
index e40e385c52bd..4c3185e2ceec 100644
--- a/drivers/platform/x86/xo15-ebook.c
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -19,13 +19,10 @@
#define MODULE_NAME "xo15-ebook"
-#define XO15_EBOOK_CLASS MODULE_NAME
#define XO15_EBOOK_TYPE_UNKNOWN 0x00
#define XO15_EBOOK_NOTIFY_STATUS 0x80
-#define XO15_EBOOK_SUBCLASS "ebook"
#define XO15_EBOOK_HID "XO15EBK"
-#define XO15_EBOOK_DEVICE_NAME "EBook Switch"
MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");
MODULE_LICENSE("GPL");
@@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev)
if (!id)
return dev_err_probe(dev, -ENODEV, "Unsupported hid\n");
- strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME);
- strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS);
-
snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id);
- input->name = acpi_device_name(device);
+ input->name = "EBook Switch";
input->phys = button->phys;
input->id.bustype = BUS_HOST;
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
index c759add4df49..1273b6082311 100644
--- a/drivers/power/supply/surface_battery.c
+++ b/drivers/power/supply/surface_battery.c
@@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
};
static const struct ssam_device_id surface_battery_match[] = {
- { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 },
- { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
- { },
+ {
+ SSAM_SDEV(BAT, SAM, 0x01, 0x00),
+ .driver_data = (unsigned long)&spwr_psy_props_bat1,
+ }, {
+ SSAM_SDEV(BAT, KIP, 0x01, 0x00),
+ .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3,
+ },
+ { }
};
MODULE_DEVICE_TABLE(ssam, surface_battery_match);
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
index 90b823848c99..d4bba6b41794 100644
--- a/drivers/power/supply/surface_charger.c
+++ b/drivers/power/supply/surface_charger.c
@@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = {
};
static const struct ssam_device_id surface_ac_match[] = {
- { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
- { },
+ {
+ SSAM_SDEV(BAT, SAM, 0x01, 0x01),
+ .driver_data = (unsigned long)&spwr_psy_props_adp1,
+ },
+ { }
};
MODULE_DEVICE_TABLE(ssam, surface_ac_match);
diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h
index dc59c43bd8f2..271472458516 100644
--- a/include/linux/amd-pmf-io.h
+++ b/include/linux/amd-pmf-io.h
@@ -66,15 +66,6 @@ enum sfh_dev_mode {
SFH_MODE_TABLET = 3,
};
-enum laptop_placement {
- LP_UNKNOWN = 0,
- ON_TABLE,
- ON_LAP_MOTION,
- IN_BAG,
- OUT_OF_BAG,
- LP_UNDEFINED,
-};
-
/**
* struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver
* @npuclk_freq: NPU clock frequency [MHz]
@@ -83,6 +74,7 @@ enum laptop_placement {
* @mpnpuclk_freq: MPNPU [MHz]
* @npu_reads: NPU read bandwidth [MB/sec]
* @npu_writes: NPU write bandwidth [MB/sec]
+ * @npu_temp: NPU temperature [C]
*/
struct amd_pmf_npu_metrics {
u16 npuclk_freq;
@@ -91,6 +83,7 @@ struct amd_pmf_npu_metrics {
u16 mpnpuclk_freq;
u16 npu_reads;
u16 npu_writes;
+ u16 npu_temp;
};
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index c29962d5baac..b5ed8c83ace1 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
int asus_hid_register_listener(struct asus_hid_listener *cdev);
void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
int asus_hid_event(enum asus_hid_event event);
+bool asus_wmi_custom_fan_curve_is_enabled(void);
#else
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
{
@@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event)
{
return -ENODEV;
}
+static inline bool asus_wmi_custom_fan_curve_is_enabled(void)
+{
+ return false;
+}
#endif
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h
new file mode 100644
index 000000000000..24cf9926dc59
--- /dev/null
+++ b/include/uapi/linux/amd-pmf.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+/*
+ * AMD Platform Management Framework (PMF) UAPI Header
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * This file defines the user-space API for interacting with the AMD PMF
+ * driver. It provides ioctl interfaces to query platform-specific metrics
+ * such as power source, slider position, platform type, laptop placement,
+ * and various BIOS input/output parameters.
+ */
+
+#ifndef _UAPI_LINUX_AMD_PMF_H
+#define _UAPI_LINUX_AMD_PMF_H
+
+#include <linux/bits.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands
+ *
+ * This magic number uniquely identifies AMD PMF ioctl operations.
+ */
+#define AMD_PMF_IOC_MAGIC 'p'
+
+/**
+ * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data
+ *
+ * This ioctl command is used to populate the amd_pmf_info structure
+ * with the requested PMF metrics information.
+ */
+#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64)
+
+#define AMD_PMF_BIOS_PARAMS_MAX 10
+
+/* AMD PMF feature flags - bitmask indicating supported features */
+#define AMD_PMF_FEAT_AUTO_MODE BIT(0)
+#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1)
+#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2)
+#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3)
+#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4)
+
+/**
+ * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop
+ * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined
+ * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk
+ * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion
+ * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case
+ * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case
+ * @AMD_PMF_LP_UNDEFINED: Placement state is undefined
+ *
+ * This enumeration represents the physical placement state of the laptop
+ * as detected by platform sensors. Used for adaptive power management
+ * and thermal policies.
+ */
+enum amd_pmf_laptop_placement {
+ AMD_PMF_LP_UNKNOWN,
+ AMD_PMF_ON_TABLE,
+ AMD_PMF_ON_LAP_MOTION,
+ AMD_PMF_IN_BAG,
+ AMD_PMF_OUT_OF_BAG,
+ AMD_PMF_LP_UNDEFINED,
+};
+
+/**
+ * enum amd_pmf_ta_slider - Trusted Application power slider positions
+ * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance
+ * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life
+ * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance
+ * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption
+ * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value
+ *
+ * This enumeration defines the power slider positions used by the
+ * AMD PMF Trusted Application for dynamic power management decisions.
+ * These correspond to the Windows power slider UI positions.
+ */
+enum amd_pmf_ta_slider {
+ AMD_PMF_TA_BEST_BATTERY,
+ AMD_PMF_TA_BETTER_BATTERY,
+ AMD_PMF_TA_BETTER_PERFORMANCE,
+ AMD_PMF_TA_BEST_PERFORMANCE,
+ AMD_PMF_TA_MAX,
+};
+
+/**
+ * enum amd_pmf_platform_type - Describes the physical form factor orientation
+ * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined
+ * @AMD_PMF_LID_CLOSE: Laptop lid is closed
+ * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen
+ * @AMD_PMF_FLAT: Device is lying flat on a surface
+ * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing)
+ * @AMD_PMF_STAND: Device is propped up in stand orientation
+ * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden
+ * @AMD_PMF_BOOK: Device is in book reading orientation
+ * @AMD_PMF_PRESENTATION: Device is in presentation mode
+ * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user
+ * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker
+ *
+ * This enumeration describes the current physical orientation or form
+ * factor of convertible/2-in-1 devices. Used for optimizing power and
+ * thermal management based on device posture.
+ */
+enum amd_pmf_platform_type {
+ AMD_PMF_PTYPE_UNKNOWN,
+ AMD_PMF_LID_CLOSE,
+ AMD_PMF_CLAMSHELL,
+ AMD_PMF_FLAT,
+ AMD_PMF_TENT,
+ AMD_PMF_STAND,
+ AMD_PMF_TABLET,
+ AMD_PMF_BOOK,
+ AMD_PMF_PRESENTATION,
+ AMD_PMF_PULL_FWD,
+ AMD_PMF_PTYPE_INVALID = 0xf,
+};
+
+static inline const char *amd_pmf_get_platform_type(unsigned int platform_type)
+{
+ switch (platform_type) {
+ case AMD_PMF_CLAMSHELL:
+ return "CLAMSHELL";
+ case AMD_PMF_LID_CLOSE:
+ return "LID_CLOSE";
+ case AMD_PMF_FLAT:
+ return "FLAT";
+ case AMD_PMF_TENT:
+ return "TENT";
+ case AMD_PMF_STAND:
+ return "STAND";
+ case AMD_PMF_TABLET:
+ return "TABLET";
+ case AMD_PMF_BOOK:
+ return "BOOK";
+ case AMD_PMF_PRESENTATION:
+ return "PRESENTATION";
+ case AMD_PMF_PULL_FWD:
+ return "PULL_FWD";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state)
+{
+ switch (device_state) {
+ case AMD_PMF_ON_TABLE:
+ return "ON_TABLE";
+ case AMD_PMF_ON_LAP_MOTION:
+ return "ON_LAP_MOTION";
+ case AMD_PMF_IN_BAG:
+ return "IN_BAG";
+ case AMD_PMF_OUT_OF_BAG:
+ return "OUT_OF_BAG";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static inline const char *amd_pmf_get_slider_position(unsigned int state)
+{
+ switch (state) {
+ case AMD_PMF_TA_BEST_PERFORMANCE:
+ return "PERFORMANCE";
+ case AMD_PMF_TA_BETTER_PERFORMANCE:
+ return "BALANCED";
+ case AMD_PMF_TA_BEST_BATTERY:
+ return "POWER_SAVER";
+ case AMD_PMF_TA_BETTER_BATTERY:
+ return "BALANCED_BATTERY";
+ default:
+ return "Unknown TA Slider State";
+ }
+}
+
+struct amd_pmf_info {
+ __u64 size;
+
+ /* Feature info */
+ __u32 features_supported;
+
+ /* Power and state info */
+ __u32 platform_type;
+ __u32 power_source;
+ __u32 laptop_placement;
+ __u32 lid_state;
+ __u32 user_presence;
+ __u32 slider_position;
+
+ /* Thermal and power metrics */
+ __s32 skin_temp;
+ __u32 gfx_busy;
+ __s32 ambient_light;
+ __u32 avg_c0_residency;
+ __u32 max_c0_residency;
+ __u32 socket_power;
+
+ /* BIOS parameters */
+ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX];
+ __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX];
+};
+
+#endif /* _UAPI_LINUX_AMD_PMF_H */
diff --git a/tools/platform/x86/amd/Makefile b/tools/platform/x86/amd/Makefile
new file mode 100644
index 000000000000..5d820df5871f
--- /dev/null
+++ b/tools/platform/x86/amd/Makefile
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: GPL-2.0
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(CURDIR)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+endif
+
+# Include common tools build infrastructure
+include $(srctree)/tools/scripts/Makefile.include
+
+CC = $(CROSS_COMPILE)gcc
+BUILD_OUTPUT := $(CURDIR)
+PREFIX ?= /usr
+DESTDIR ?=
+
+ifeq ("$(origin O)", "command line")
+ BUILD_OUTPUT := $(O)
+endif
+
+# Include paths: tools/include has linux/kernel.h with ARRAY_SIZE
+INCLUDES = -I$(srctree)/tools/include
+INCLUDES += -I$(srctree)/tools/include/uapi
+INCLUDES += -I$(srctree)/include/uapi
+INCLUDES += -I$(srctree)/include
+
+override CFLAGS += -O2 -Wall -Wextra -D_GNU_SOURCE $(INCLUDES)
+override CFLAGS += -D_FILE_OFFSET_BITS=64
+override CFLAGS += -D__EXPORTED_HEADERS__
+
+TARGETS = test_amd_pmf
+
+all: $(TARGETS)
+
+test_amd_pmf: test-pmf.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $< -o $(BUILD_OUTPUT)/$@ $(LDFLAGS)
+
+.PHONY: clean
+clean:
+ $(call QUIET_CLEAN, test_amd_pmf)$(RM) $(BUILD_OUTPUT)/test_amd_pmf
+
+.PHONY: install
+install: $(TARGETS)
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) $(BUILD_OUTPUT)/test_amd_pmf $(DESTDIR)$(PREFIX)/bin/test_amd_pmf
+
+.PHONY: help
+help:
+ @echo "AMD Platform Tools Makefile"
+ @echo ""
+ @echo "Targets:"
+ @echo " all - Build all tools (default)"
+ @echo " test_amd_pmf - Build the PMF test tool"
+ @echo " clean - Remove built files"
+ @echo " install - Install tools to $(PREFIX)/bin"
+ @echo ""
+ @echo "Variables:"
+ @echo " O=<dir> - Build output directory"
+ @echo " PREFIX - Installation prefix (default: /usr)"
+ @echo " DESTDIR - Destination directory for install"
diff --git a/tools/platform/x86/amd/test-pmf.c b/tools/platform/x86/amd/test-pmf.c
new file mode 100644
index 000000000000..82978afa6e1b
--- /dev/null
+++ b/tools/platform/x86/amd/test-pmf.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Test Tool
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Sanket Goswami <Sanket.Goswami@amd.com>
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <linux/amd-pmf.h>
+#include <linux/kernel.h>
+
+#define DEVICE_NODE "/dev/amdpmf_interface"
+
+/* Feature flag names */
+static const char * const feature_names[] = {
+ "Auto Mode",
+ "Static Power Slider",
+ "Policy Builder (Smart PC)",
+ "Dynamic Power Slider AC",
+ "Dynamic Power Slider DC",
+};
+
+static const char *banner =
+ "====================================================\n"
+ " AMD PMF Metrics info and Feature Status\n"
+ "====================================================\n\n";
+
+/* Print feature flags */
+static void pmf_print_features(uint32_t flags)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(feature_names); i++)
+ printf(" [%c] %s\n", (flags & (1U << i)) ? 'x' : ' ', feature_names[i]);
+}
+
+/* Print BIOS parameters */
+static void pmf_print_bios_params(const char *type, const __u32 *params)
+{
+ int i;
+
+ for (i = 0; i < AMD_PMF_BIOS_PARAMS_MAX; i++)
+ printf(" Custom BIOS %s%d: %u\n", type, i + 1, params[i]);
+}
+
+/* Open the PMF device */
+static int pmf_open_device(void)
+{
+ int fd;
+
+ fd = open(DEVICE_NODE, O_RDONLY);
+ if (fd < 0)
+ fprintf(stderr, "Error: Cannot open %s: %s\n", DEVICE_NODE, strerror(errno));
+
+ return fd;
+}
+
+/* Query PMF info using the single IOCTL */
+static int pmf_get_info(int fd, struct amd_pmf_info *info)
+{
+ int ret;
+
+ /* Zero-initialize and set size for versioning */
+ memset(info, 0, sizeof(*info));
+ info->size = sizeof(*info);
+
+ ret = ioctl(fd, IOCTL_AMD_PMF_POPULATE_DATA, info);
+ if (ret < 0) {
+ fprintf(stderr, "Error: IOCTL_AMD_PMF_POPULATE_DATA failed: %s\n", strerror(errno));
+ return ret;
+ }
+
+ return 0;
+}
+
+static void pmf_print_info(const struct amd_pmf_info *info)
+{
+ printf("%s", banner);
+
+ /* Feature status */
+ printf("Feature Status:\n");
+ pmf_print_features(info->features_supported);
+
+ /* Device states */
+ printf("\nDevice States:\n");
+ printf(" Platform Type: %s\n", amd_pmf_get_platform_type(info->platform_type));
+ printf(" Laptop Placement: %s\n", amd_pmf_get_laptop_placement(info->laptop_placement));
+ printf(" Lid State: %s\n", info->lid_state ? "Closed" : "Open");
+ printf(" User Presence: %s\n", info->user_presence ? "Present" : "Away");
+ printf(" Slider Position: %s\n", amd_pmf_get_slider_position(info->slider_position));
+
+ /* Thermal and power metrics */
+ printf("\nThermal/Power Metrics:\n");
+ printf(" Skin Temperature: %d\n", info->skin_temp / 100);
+ printf(" GFX Busy: %u\n", info->gfx_busy);
+ printf(" Ambient Light: %d\n", info->ambient_light);
+ printf(" Avg C0 Residency: %u\n", info->avg_c0_residency);
+ printf(" Max C0 Residency: %u\n", info->max_c0_residency);
+ printf(" Socket Power: %u\n", info->socket_power);
+
+ /* BIOS parameters */
+ printf("\nCustom BIOS Input Parameters:\n");
+ pmf_print_bios_params("Input", info->bios_input);
+ printf("\nCustom BIOS Output Parameters:\n");
+ pmf_print_bios_params("Output", info->bios_output);
+
+ printf("\n=================================================\n");
+}
+
+int main(void)
+{
+ struct amd_pmf_info info;
+ int fd, ret;
+
+ fd = pmf_open_device();
+ if (fd < 0)
+ return -1;
+
+ /* Query all info with single IOCTL */
+ ret = pmf_get_info(fd, &info);
+ close(fd);
+
+ if (ret < 0)
+ return -1;
+
+ pmf_print_info(&info);
+
+ return 0;
+}