summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/amdpmf-interface73
-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--MAINTAINERS8
-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/hid/hid-asus.c6
-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/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.c53
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c13
-rw-r--r--drivers/platform/x86/amd/pmf/Kconfig10
-rw-r--r--drivers/platform/x86/amd/pmf/Makefile2
-rw-r--r--drivers/platform/x86/amd/pmf/core.c5
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h34
-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.h124
-rw-r--r--drivers/platform/x86/asus-laptop.c9
-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-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/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.c16
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.h8
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c11
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/int-attributes.c4
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c8
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c6
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/string-attributes.c4
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c270
-rw-r--r--drivers/platform/x86/huawei-wmi.c99
-rw-r--r--drivers/platform/x86/intel/ishtp_eclite.c5
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c127
-rw-r--r--drivers/platform/x86/lenovo/ymc.c8
-rw-r--r--drivers/platform/x86/lg-laptop.c644
-rw-r--r--drivers/platform/x86/msi-ec.c81
-rw-r--r--drivers/platform/x86/msi-wmi.c97
-rw-r--r--drivers/platform/x86/panasonic-laptop.c4
-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.c609
-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.h9
-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
70 files changed, 3067 insertions, 637 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-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/MAINTAINERS b/MAINTAINERS
index e8091174eb9a..4680927e6d0d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1146,6 +1146,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>
@@ -1264,6 +1271,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>
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 837d59e7a661..8ac44368a37d 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/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
@@ -102,20 +103,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/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 3f5e96900b67..d34d74df3dc0 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -965,6 +965,12 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
}
}
+ if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
+ drvdata->fn_lock = true;
+ INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
+ asus_kbd_set_fn_lock(hdev, true);
+ }
+
if (drvdata->tp) {
int ret;
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/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..72f68cef1297 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -21,6 +21,7 @@
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/topology.h>
#include <linux/uuid.h>
@@ -77,6 +78,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 +107,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 +116,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 +142,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 +151,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 +192,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 +207,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 +224,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 +231,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,
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index 6a26937fc2b5..1a87931136fd 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -223,6 +223,19 @@ 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.
+ *
+ * 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;
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..bf7aad80b9e9 100644
--- a/drivers/platform/x86/amd/pmf/Makefile
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -8,3 +8,5 @@ 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
+# 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..58d86b4c2828 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -634,6 +634,10 @@ static int amd_pmf_probe(struct platform_device *pdev)
pmf_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");
return 0;
@@ -643,6 +647,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/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 69fef7448744..752fa5dd2267 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -130,6 +130,7 @@ struct cookie_header {
#define GET_CMD true
#define METRICS_TABLE_ID 7
+#define BIOS_OUTPUT_MAX 10
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
@@ -442,6 +443,7 @@ 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];
};
struct apmf_sps_prop_granular_v2 {
@@ -680,14 +682,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 +713,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,
@@ -923,9 +903,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..afcb517dc5bf 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) {
@@ -2081,6 +2147,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 +2419,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-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-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/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..78019644ec35 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -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..21077d17113b 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 */
@@ -300,10 +301,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 +315,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..f2fd966c9ca4 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -275,10 +275,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 +288,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..863e486474ad 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:
@@ -298,10 +298,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 +311,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..ed5e2080f22b 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -385,10 +385,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 +398,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/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
index fe5a9a3a4ef1..f98c32dacbc7 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -263,10 +263,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 +277,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..5353d997d272 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -133,11 +133,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.
@@ -187,87 +228,95 @@ 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, "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, "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, "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,
},
{},
};
@@ -500,9 +549,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 +878,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 +908,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)
@@ -1789,6 +1836,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 +1953,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 +1968,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 +2006,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 +2277,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 +2309,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 +2487,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 +2499,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 +2532,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 +2561,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 +2571,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 +2601,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 +2618,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 +2629,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 +2658,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 +2699,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 +2745,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 +2786,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 +2839,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/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/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index b528a1e67dd3..aaeada46a0e7 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);
}
@@ -11285,6 +11290,111 @@ 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;
+
+ 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 +11455,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&dprc_attr_group,
&auxmac_attr_group,
&hwdd_attr_group,
+ &usbc_security_attr_group,
NULL,
};
@@ -11499,6 +11610,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 +12077,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/ymc.c b/drivers/platform/x86/lenovo/ymc.c
index 1b73a55f1b89..015e046b0fce 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,6 +22,8 @@
#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");
@@ -85,7 +89,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);
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..667df9509798 100644
--- a/drivers/platform/x86/msi-ec.c
+++ b/drivers/platform/x86/msi-ec.c
@@ -1130,6 +1130,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 +1225,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/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 719add753cb3..3effb11c4153 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"
@@ -1017,8 +1015,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/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..f55b239bd4d1 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,11 @@
#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)
+#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 +348,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 +365,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 +379,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 +393,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 single_color_kbd;
+ 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 +426,8 @@ struct uniwill_battery_entry {
struct uniwill_device_descriptor {
unsigned int features;
+ u8 kbd_led_max_brightness;
+ u8 lightbar_max_brightness;
/* Executed during driver probing */
int (*probe)(struct uniwill_data *data);
};
@@ -427,6 +479,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 +490,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 +498,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 +593,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 +601,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 +633,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 +643,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 +682,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 +1145,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 +1259,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 +1300,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 +1515,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 +1584,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 +1604,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 +1624,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->single_color_kbd)
+ 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_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 = {
+ .devicename = DRIVER_NAME,
+ .devname_mandatory = true,
+ };
+ bool intensity_all_zeros = true;
+ bool needs_trigger = false;
+ 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;
+
+ switch (data->project_id) {
+ case PROJECT_ID_PF:
+ case PROJECT_ID_PF4MU_PF4MN_PF5MU:
+ case PROJECT_ID_PH4TRX1:
+ case PROJECT_ID_PH4TUX1:
+ case PROJECT_ID_PH4TQX1:
+ case PROJECT_ID_PH6TRX1:
+ case PROJECT_ID_PH6TQXX:
+ case PROJECT_ID_PHXAXXX:
+ case PROJECT_ID_PHXPXXX:
+ data->single_color_kbd = true;
+ break;
+ default:
+ data->single_color_kbd = regval & KBD_WHITE_ONLY;
+ break;
+ }
+
+ if (data->single_color_kbd) {
+ init_data.default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT;
+ 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);
+ }
+
+ 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;
+
+ init_data.default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT;
+ 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 unsigned int uniwill_sanitize_battery_threshold(unsigned int value)
{
/* 0 means "charging threshold not active" */
@@ -1789,6 +2212,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 +2290,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 +2334,8 @@ static int uniwill_probe(struct platform_device *pdev)
return ret;
data->features = device_descriptor.features;
+ 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 +2356,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 +2431,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 +2498,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 +2552,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->single_color_kbd)
+ 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 +2621,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 +2659,29 @@ 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_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 +2705,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,
};
/*
@@ -2277,8 +2827,37 @@ 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_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"),
@@ -2745,6 +3324,10 @@ 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 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 55198d2875cc..e014d4ce5a20 100644
--- a/include/linux/amd-pmf-io.h
+++ b/include/linux/amd-pmf-io.h
@@ -52,15 +52,6 @@ struct amd_sfh_info {
u32 laptop_placement;
};
-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]
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;
+}