Files
omarchy-pkgs/pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch
T

8291 lines
255 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h
--- a/arch/x86/include/uapi/asm/amd_hsmp.h
+++ b/arch/x86/include/uapi/asm/amd_hsmp.h
@@ -53,9 +53,21 @@ enum hsmp_message_ids {
HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */
HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */
HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */
+ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */
+ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */
HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */
HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */
HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */
+ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */
+ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */
+ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */
+ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */
+ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */
+ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */
+ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */
+ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */
+ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */
+ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */
HSMP_MSG_ID_MAX,
};
@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{0, 1, HSMP_GET},
/*
- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0
- * input: args[0] = min link width[15:8] + max link width[7:0]
+ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) +
+ * min link width[15:8] + max link width[7:0]
+ * Link width encoding: 0 = x4, 1 = x8, 2 = x16.
+ * On SET, max must be >= min. On GET, [15:0] are reserved.
+ * output: args[0] = reserved[31:16] + min link width[15:8] +
+ * max link width[7:0]
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/*
- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0
- * input: args[0] = df pstate[7:0]
+ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1
+ * input: args[0] = set APB_DISABLE / get APB state[31]
+ * (0 = set & lock DF P-state, 1 = get) +
+ * reserved[30:8] +
+ * DF P-state[7:0] (0..2; reserved on GET)
+ * output: args[0] = reserved[31:9] +
+ * APB state[8] (1 = disabled, 0 = enabled) +
+ * locked DF P-state[7:0] if [8] = 1, else reserved
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */
{0, 0, HSMP_SET},
@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{1, 1, HSMP_SET},
/*
- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0
- * input: args[0] = power efficiency mode[2:0]
+ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection),
+ * num_args = 1, response_sz = 1
+ * input: args[0] = set/get policy[31] (0 = set, 1 = get) +
+ * high util point[30:24] +
+ * low util point[23:17] +
+ * PPT limit[16:5] +
+ * reserved[4:3] + mode selection[2:0]
+ * [30:5] are valid only when [2:0] is a balanced core mode
+ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1).
+ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated
+ * current efficiency mode.
*/
{1, 1, HSMP_SET_GET},
/*
- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0
- * input: args[0] = min df pstate[15:8] + max df pstate[7:0]
+ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) +
+ * reserved[30:16] +
+ * min DF P-state[15:8] + max DF P-state[7:0]
+ * DF P-state encoding: 0 = DFP0 (high performance),
+ * 1 = DFP1, 2 = DFP2 (low performance).
+ * [15:0] are reserved when getting (args[0] bit[31] = 1).
+ * output: args[0] = reserved[31:16] + min DF P-state[15:8] +
+ * max DF P-state[7:0]
*/
- {1, 0, HSMP_SET},
+ {1, 1, HSMP_SET_GET},
/*
* HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1
@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
{0, 0, HSMP_GET},
/*
- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2
+ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3
* output: args[0] = lower 32 bits of the address
* output: args[1] = upper 32 bits of the address
+ * output: args[2] = DRAM region size in bytes
*/
- {0, 2, HSMP_GET},
+ {0, 3, HSMP_GET},
/*
* HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0
@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
*/
{1, 1, HSMP_SET_GET},
- /* RESERVED(0x29-0x2f) */
- {0, 0, HSMP_RSVD},
- {0, 0, HSMP_RSVD},
+ /*
+ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1
+ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) +
+ * reserved[30:1] +
+ * enable PC6[0] (0 = disable, 1 = enable;
+ * reserved on GET)
+ * output: args[0] = reserved[31:1] + current PC6 control[0]
+ * (last value configured via HSMP or APML)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1
+ * Configures CC6 enable for all cores; changing the setting does
+ * not by itself transition cores in or out of CC6.
+ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) +
+ * reserved[30:1] +
+ * enable CC6[0] (0 = disable, 1 = enable;
+ * reserved on GET)
+ * output: args[0] = reserved[31:1] + current CC6 control[0]
+ * (last value configured via HSMP or APML)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /* RESERVED(0x2B-0x2F) */
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
*/
{0, 2, HSMP_GET},
+ /*
+ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1
+ * input: args[0] = reg space[23] + reg offset[22:12] +
+ * device LID[11:8] + DIMM address[7:0]
+ * output: args[0] = read data byte[3:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1
+ * input: args[0] = apic id of core[15:0]
+ * output: args[0] = CCD power(mWatts)[31:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1
+ * output: args[0] = thermal behaviour[31:0]
+ */
+ {0, 1, HSMP_GET},
+
+ /*
+ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1
+ * input: args[0] = SVI3 rail index[3:1] + read temperature[0]
+ * output: args[0] = SVI3 rail index[30:28] +
+ * rail temperature in degree C[27:0]
+ */
+ {1, 1, HSMP_GET},
+
+ /*
+ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3
+ * input: args[0] = HSMP command mask[0]
+ * output: status of HSMP command = args[0], args[1], args[2]
+ */
+ {1, 3, HSMP_GET},
+
+ /*
+ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1
+ * input: args[0] = op[31:30] + reserved[29:28] +
+ * apic id[27:16] + floor frequency MHz[15:0]
+ * op encoding: 00 = set per-core floor,
+ * 01 = set all-cores floor (apic id reserved),
+ * 10 = get per-core floor,
+ * 11 = get per-core effective floor.
+ * Floor frequency field is reserved on GET (bit[31] = 1).
+ * output: args[0] = floor frequency MHz[15:0]
+ * (effective for op 11, configured for op 10;
+ * reserved on SET)
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0
+ * input: args[0] = write data[31:24] + reg space[23] +
+ * reg offset[22:12] + device LID[11:8] +
+ * DIMM address[7:0]
+ */
+ {1, 0, HSMP_SET},
+
+ /*
+ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1
+ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) +
+ * SDPS limit[30:0]
+ * output: args[0] = SDPS limit[30:0]
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1
+ * input: args[0] = op[31:30] + priority sel[27:26] +
+ * priority val[21:20] + input[19:0]
+ * output: args[0] = supported priorities or priority val[1:0]
+ */
+ {1, 1, HSMP_SET_GET},
+
+ /*
+ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2
+ * input: args[0] = op[31] + sub-op[30:29] + params[28:0]
+ * output: args[0] = discovery bits or floating/global memory BW (Gbps)
+ * output: args[1] = reserved or config (drop adj, sampling delay,
+ * hysteresis)
+ */
+ {1, 2, HSMP_SET_GET},
};
/* Metrics table (supported only with proto version 6) */
@@ -467,6 +612,39 @@ struct hsmp_metric_table {
__u32 gfxclk_frequency[8];
};
+/**
+ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL
+ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout
+ * stable between 32-bit and 64-bit callers) to the destination
+ * buffer that receives the metric table.
+ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the
+ * number of bytes copied out on success. Must be non-zero and no
+ * larger than the metric table size firmware reports for this
+ * socket; a larger value is rejected with -EINVAL rather than
+ * short-written. A smaller value returns the leading @size bytes
+ * of the snapshot. The kernel does not write this field back.
+ * @sock_ind: Input. Socket index from which the metric table is read.
+ * @reserved: Reserved for future use. Callers should set this to zero;
+ * future kernels may begin interpreting the field, so passing
+ * a non-zero value today is not forwards compatible.
+ *
+ * Placing @buf first lets all fields fall on their natural alignment under
+ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with
+ * the same wire layout on 32-bit and 64-bit userspace.
+ *
+ * The metric table layout depends on the HSMP protocol version reported by
+ * firmware, which userspace can read from the protocol_version sysfs
+ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on
+ * that version pass sizeof(struct hsmp_metric_table). Later version metrics
+ * table layout is documented in the Public PPR.
+ */
+struct hsmp_telemetry_data {
+ __u64 buf;
+ __u32 size;
+ __u16 sock_ind;
+ __u16 reserved;
+};
+
/* Reset to default packing */
#pragma pack()
@@ -474,4 +652,16 @@ struct hsmp_metric_table {
#define HSMP_BASE_IOCTL_NR 0xF8
#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message)
+/*
+ * Fetch the firmware metric (telemetry) table for a given socket via the
+ * HSMP character device. This avoids the PAGE_SIZE limitation of the
+ * sysfs binary attribute path for tables larger than one page (such as the
+ * ~13 KB table used by HSMP protocol version 7).
+ *
+ * The direction is _IOW because the kernel only reads the request struct;
+ * the table itself is written to the buffer that @buf points at.
+ */
+#define HSMP_IOCTL_GET_TELEMETRY_DATA \
+ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data)
+
#endif /*_ASM_X86_AMD_HSMP_H_*/
diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c
--- 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
--- 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
--- 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/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- /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
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -15,12 +15,18 @@
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/ioport.h>
+#include <linux/kref.h>
#include <linux/kstrtox.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/topology.h>
#include <linux/uuid.h>
@@ -38,6 +44,17 @@
static struct hsmp_plat_device *hsmp_pdev;
+/*
+ * Tracks the ACPI socket platform devices that share the socket array and the
+ * /dev/hsmp misc device. The first probe initializes it, each further probe
+ * takes a reference and every remove (or probe failure) drops one; the last
+ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run
+ * under hsmp_sock_rwsem held for write, so the counting is already serialized
+ * and the atomic in kref is not strictly needed; kref is used for the clearer
+ * get/put interface and its release callback.
+ */
+static struct kref hsmp_acpi_sock_kref;
+
struct hsmp_sys_attr {
struct device_attribute dattr;
u32 msg_id;
@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
* bytes to integer.
*/
uid = acpi_device_uid(ACPI_COMPANION(dev));
+ if (!uid || strlen(uid) < 3)
+ return -EINVAL;
return kstrtou16(uid + 2, 10, sock_ind);
}
@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
return AE_OK;
}
-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
+static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *guid, *mailbox_package;
@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
int ret = 0;
int j;
- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
+ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
&buf, ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
+ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
acpi_format_exception(status));
return -ENODEV;
}
@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
guid = &dsd->package.elements[0];
mailbox_package = &dsd->package.elements[1];
if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
- dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
+ dev_err(dev, "Invalid hsmp _DSD table data\n");
ret = -EINVAL;
goto free_buf;
}
@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
union acpi_object *msgobj, *msgstr, *msgint;
msgobj = &mailbox_package->package.elements[j];
- msgstr = &msgobj->package.elements[0];
- msgint = &msgobj->package.elements[1];
/* package should have 1 string and 1 integer object */
if (msgobj->type != ACPI_TYPE_PACKAGE ||
- msgstr->type != ACPI_TYPE_STRING ||
+ msgobj->package.count < 2) {
+ ret = -EINVAL;
+ goto free_buf;
+ }
+
+ msgstr = &msgobj->package.elements[0];
+ msgint = &msgobj->package.elements[1];
+
+ if (msgstr->type != ACPI_TYPE_STRING ||
msgint->type != ACPI_TYPE_INTEGER) {
ret = -EINVAL;
goto free_buf;
@@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
return ret;
}
-static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
+static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock)
{
acpi_status status;
- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
+ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS,
hsmp_resource, sock);
if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
+ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
acpi_format_exception(status));
return -EINVAL;
}
@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
return -EINVAL;
/* The mapped region should be un-cached */
- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
+ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr,
sock->mbinfo.size);
if (!sock->virt_base_addr) {
- dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
+ dev_err(dev, "Failed to ioremap MP1 base address\n");
return -ENOMEM;
}
@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
int ret;
sock->sock_ind = sock_ind;
- sock->dev = dev;
sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr;
sema_init(&sock->hsmp_sem, 1);
@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
dev_set_drvdata(dev, sock);
/* Read MP1 base address from CRS method */
- ret = hsmp_read_acpi_crs(sock);
+ ret = hsmp_read_acpi_crs(dev, sock);
if (ret)
return ret;
/* Read mailbox offsets from DSD table */
- return hsmp_read_acpi_dsd(sock);
+ ret = hsmp_read_acpi_dsd(dev, sock);
+ if (ret)
+ return ret;
+
+ /*
+ * Publish sock->dev last. hsmp_send_message() uses it (via
+ * smp_load_acquire()) as the readiness gate for the lock-free data
+ * plane, so it must become visible only after virt_base_addr, the
+ * mailbox offsets and the semaphore are fully initialized. On a
+ * multi-socket system socket 0 exposes /dev/hsmp before later sockets
+ * finish probing, so without this an ioctl aimed at a socket still in
+ * bring-up could pass the gate and dereference a NULL virt_base_addr.
+ */
+ smp_store_release(&sock->dev, dev);
+
+ return 0;
}
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj
struct device *dev = container_of(kobj, struct device, kobj);
struct hsmp_socket *sock = dev_get_drvdata(dev);
+ /*
+ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE.
+ * It can therefore only carry the protocol version 6 metric table
+ * (struct hsmp_metric_table). The larger tables defined from protocol
+ * version 7 onwards do not fit; userspace on those systems must read
+ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp.
+ * Surface the unsupported case here as -EOPNOTSUPP rather than
+ * silently truncating the snapshot.
+ */
+ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6)
+ return -EOPNOTSUPP;
+
return hsmp_metric_tbl_read(sock, buf, count);
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
const struct bin_attribute *battr, int id)
{
- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
+ /*
+ * Keep metrics_bin visible on protocol version 7 and later as well,
+ * so that userspace which expects the file to exist gets a clear
+ * -EOPNOTSUPP from the read handler instead of -ENOENT, and is
+ * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path.
+ */
+ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
return battr->attr.mode;
return 0;
@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att
return len;
}
+/*
+ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox
+ * handshake and register its sysfs/hwmon interfaces.
+ *
+ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the
+ * per-socket bring-up cannot race a concurrent probe or remove.
+ */
static int init_acpi(struct device *dev)
{
u16 sock_ind;
int ret;
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
ret = hsmp_get_uid(dev, &sock_ind);
if (ret)
return ret;
@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev)
return ret;
}
- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
+ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(sock_ind);
if (ret)
dev_info(dev, "Failed to init metric table\n");
@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
+/*
+ * kref release: tear down the shared ACPI socket state once the last socket
+ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any
+ * metric-table DRAM, destroy the per-socket mutexes and free the socket array.
+ *
+ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove
+ * and probe-failure paths both drop their reference under that lock. The write
+ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox
+ * and freeing the array cannot race the data plane.
+ */
+static void hsmp_acpi_sock_release(struct kref *kref)
+{
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
+ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device))
+ hsmp_misc_deregister();
+ hsmp_unmap_metric_tbls(hsmp_pdev);
+ hsmp_destroy_metric_read_locks(hsmp_pdev);
+ kfree(hsmp_pdev->sock);
+ hsmp_pdev->sock = NULL;
+ hsmp_pdev->num_sockets = 0;
+ hsmp_pdev->proto_ver = 0;
+}
+
+/**
+ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe.
+ * @dev: ACPI companion device whose probe failed.
+ *
+ * This device already took a reference on entry to hsmp_acpi_probe(), so clear
+ * its sock->dev and drop that reference; the shared state is released if it was
+ * the last one.
+ *
+ * Clearing sock->dev matters on multi-socket systems: when a non-first socket
+ * fails, the array stays alive (owned by an already-probed socket) and
+ * remove() is never called for this device, yet devres unmaps its mailbox once
+ * probe() returns. Without clearing dev, a later message to this index would
+ * pass every gate in hsmp_send_message() and reach the unmapped mailbox.
+ *
+ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata.
+ *
+ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write.
+ */
+static void hsmp_acpi_probe_failure_cleanup(struct device *dev)
+{
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+
+ lockdep_assert_held_write(&hsmp_sock_rwsem);
+
+ if (sock)
+ sock->dev = NULL;
+
+ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
+}
+
static int hsmp_acpi_probe(struct platform_device *pdev)
{
int ret;
@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev)
return -ENOMEM;
- if (!hsmp_pdev->is_probed) {
+ /*
+ * Multiple ACPI socket devices probe in parallel, but the one-time
+ * socket-array allocation and /dev/hsmp registration below must run
+ * exactly once. Hold the socket rwsem for write across the whole
+ * bring-up so it cannot race a concurrent probe or remove, and so the
+ * probe-failure teardown drains the data plane.
+ */
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
+ if (!hsmp_pdev->sock) {
hsmp_pdev->num_sockets = topology_max_packages();
if (!hsmp_pdev->num_sockets) {
dev_err(&pdev->dev, "No CPU sockets detected\n");
return -ENODEV;
}
- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
- sizeof(*hsmp_pdev->sock),
- GFP_KERNEL);
+ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets,
+ sizeof(*hsmp_pdev->sock),
+ GFP_KERNEL);
if (!hsmp_pdev->sock)
return -ENOMEM;
+
+ hsmp_init_metric_read_locks(hsmp_pdev);
+ kref_init(&hsmp_acpi_sock_kref);
+ } else {
+ kref_get(&hsmp_acpi_sock_kref);
}
+ /*
+ * This socket now holds a reference (kref_init on the first socket,
+ * kref_get afterwards). Every failure path below drops it via
+ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to
+ * hsmp_acpi_remove().
+ */
ret = init_acpi(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
+ hsmp_acpi_probe_failure_cleanup(&pdev->dev);
return ret;
}
- if (!hsmp_pdev->is_probed) {
- ret = hsmp_misc_register(&pdev->dev);
+ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) {
+ /*
+ * Register /dev/hsmp unparented. It is a singleton shared by all
+ * ACPI sockets and outlives all but the last of them, so
+ * parenting it to this socket's device would leave a dangling
+ * parent once that socket is unbound.
+ */
+ ret = hsmp_misc_register(NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to register misc device\n");
+ hsmp_acpi_probe_failure_cleanup(&pdev->dev);
return ret;
}
- hsmp_pdev->is_probed = true;
- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n");
+ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n");
}
return 0;
@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
static void hsmp_acpi_remove(struct platform_device *pdev)
{
+ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev);
+
/*
- * We register only one misc_device even on multi-socket system.
- * So, deregister should happen only once.
+ * Serialize the kref_put() and any release it triggers against a
+ * concurrent probe, and drain the data plane for the whole
+ * teardown: this covers the per-socket unbind, whose mailbox devres
+ * unmaps once we return, and the last unbind that frees the socket
+ * array in hsmp_acpi_sock_release().
*/
- if (hsmp_pdev->is_probed) {
- hsmp_misc_deregister();
- hsmp_pdev->is_probed = false;
- }
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
+ /*
+ * Clear this socket's dev so hsmp_send_message() rejects it before
+ * devres unmaps the mailbox. On a non-final unbind the socket array
+ * stays alive, so without this a later message to this index would
+ * reach an unmapped iomem region.
+ */
+ sock->dev = NULL;
+
+ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
}
static struct platform_driver amd_hsmp_driver = {
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -10,10 +10,17 @@
#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/nospec.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
+#include <linux/slab.h>
#include <linux/sysfs.h>
+#include <linux/uaccess.h>
#include "hsmp.h"
@@ -40,6 +47,17 @@
static struct hsmp_plat_device hsmp_pdev;
+/*
+ * Gates the AMD HSMP data plane against socket bring-up and teardown.
+ *
+ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads
+ * run concurrently. Probe and remove take it for write: probe brings sockets
+ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove
+ * tears them down, both excluding and draining the data plane.
+ */
+DECLARE_RWSEM(hsmp_sock_rwsem);
+EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP");
+
/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg)
return -EINVAL;
/*
- * Some older HSMP SET messages are updated to add GET in the same message.
- * In these messages, GET returns the current value and SET also returns
- * the successfully set value. To support this GET and SET in same message
- * while maintaining backward compatibility for the HSMP users,
- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz.
+ * As the HSMP protocol evolves, newer platforms may define more
+ * response arguments for existing messages. Use an upper-bound
+ * check so that older userspace callers requesting fewer response
+ * words than what the current hsmp_msg_desc_table[] defines are
+ * still accepted, while rejecting requests that exceed the
+ * hardware capability.
*/
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) {
- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
- return -EINVAL;
- } else {
- /* only HSMP_SET or HSMP_GET messages go through this strict check */
- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
- return -EINVAL;
- }
+ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
+ return -EINVAL;
+
return 0;
}
-int hsmp_send_message(struct hsmp_message *msg)
+/*
+ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane
+ * takes it for read so many messages run concurrently, while the probe-time
+ * senders run under the write lock taken by probe. Holding it here serializes
+ * every message against socket teardown, which also holds it for write.
+ */
+static int hsmp_send_message_locked(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
unsigned int sock_ind;
int ret;
+ lockdep_assert_held(&hsmp_sock_rwsem);
+
if (!msg)
return -EINVAL;
ret = validate_message(msg);
@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg)
sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets);
sock = &hsmp_pdev.sock[sock_ind];
+ /*
+ * A slot exists for every possible socket, but it is only usable once
+ * that socket has actually been probed. Reject messages aimed at a
+ * socket that was never brought up or is still in bring-up, so we never
+ * operate on a zero-initialized semaphore or an unmapped mailbox. A
+ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and
+ * the semaphore are visible.
+ *
+ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev)
+ * in hsmp_parse_acpi_table().
+ */
+ if (!smp_load_acquire(&sock->dev))
+ return -ENODEV;
+
ret = down_interruptible(&sock->hsmp_sem);
if (ret < 0)
return ret;
@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg)
return ret;
}
+
+int hsmp_send_message(struct hsmp_message *msg)
+{
+ /*
+ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue
+ * messages from here. Take hsmp_sock_rwsem for read so messages run
+ * concurrently with each other but are drained and kept out while
+ * probe/remove hold it for write to tear a socket down.
+ */
+ guard(rwsem_read)(&hsmp_sock_rwsem);
+
+ return hsmp_send_message_locked(msg);
+}
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP");
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args)
@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value)
msg.args[0] = value;
msg.sock_ind = sock_ind;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg)
return false;
}
-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return 0;
}
-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
+static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
+ size_t size);
+
+/*
+ * Fetch the firmware metric (telemetry) table for the requested socket and
+ * copy it to the userspace buffer described by the request.
+ *
+ * The metric table size is variable across HSMP protocol versions and on
+ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer
+ * size, which may be anything up to the size firmware reported for this
+ * socket's table.
+ */
+static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg)
+{
+ void *kbuf __free(kvfree) = NULL;
+ void __user *arguser = (void __user *)arg;
+ struct hsmp_telemetry_data req;
+ struct hsmp_socket *sock;
+ void __user *user_buf;
+ size_t tbl_size;
+ unsigned int sock_ind;
+ int ret;
+
+ /* Telemetry data is read-only; require read access on the fd. */
+ if (!(fp->f_mode & FMODE_READ))
+ return -EPERM;
+
+ if (copy_from_user(&req, arguser, sizeof(req)))
+ return -EFAULT;
+
+ /*
+ * Reserved fields must be zero so future kernels can safely
+ * repurpose them without breaking already-deployed userspace.
+ */
+ if (req.reserved)
+ return -EINVAL;
+
+ user_buf = u64_to_user_ptr(req.buf);
+
+ /*
+ * /dev/hsmp is a singleton character device that outlives an individual
+ * socket unbind, so an ioctl on an already-open fd can run concurrently
+ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket
+ * lookup, the checks on its metric-table state and the read itself:
+ * probe and remove take the same lock for write, so they cannot free the
+ * socket array, unmap the table or destroy the per-socket mutex while
+ * this runs.
+ *
+ * The lock is dropped before the copy_to_user() below. Faulting in the
+ * destination can block indefinitely on a userfaultfd-backed buffer,
+ * which would leave a socket unbind waiting for the write lock.
+ */
+ scoped_guard(rwsem_read, &hsmp_sock_rwsem) {
+ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets)
+ return -ENODEV;
+
+ /*
+ * Sanitize the user-controlled socket index against speculative
+ * execution. The bounds check above retires the out-of-range
+ * case with -ENODEV, but a mispredicted branch can still let the
+ * CPU speculatively use sock_ind as an index into
+ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the
+ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns
+ * the bounds check into a data-flow clamp so the speculative
+ * load is in-range too.
+ */
+ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets);
+ sock = &hsmp_pdev.sock[sock_ind];
+ if (!sock->metric_tbl_addr)
+ return -ENODEV;
+
+ tbl_size = sock->metric_tbl_size;
+ if (!tbl_size)
+ return -ENODEV;
+
+ /*
+ * A request shorter than the firmware table is served with the
+ * leading @size bytes of the snapshot, so userspace built
+ * against an older table layout keeps working on firmware that
+ * grew the table. Asking for more than firmware provides is
+ * rejected rather than short-written, so a caller can never
+ * mistake a partial copy for a full one.
+ */
+ if (!req.size || req.size > tbl_size)
+ return -EINVAL;
+
+ /*
+ * The bounce buffer is overwritten in full by memcpy_fromio()
+ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid
+ * the zeroing cost of kvzalloc() on the ~13 KB allocation done
+ * on every ioctl call.
+ */
+ kbuf = kvmalloc(tbl_size, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(user_buf, kbuf, req.size))
+ return -EFAULT;
+
+ return 0;
+}
+
+long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case HSMP_IOCTL_CMD:
+ return hsmp_ioctl_msg(fp, arg);
+ case HSMP_IOCTL_GET_TELEMETRY_DATA:
+ return hsmp_ioctl_get_telemetry(fp, arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping
+ * and its metric_read_lock alive: probe and remove take the same lock for
+ * write while they bring sockets up and tear them down.
+ */
+static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
+ size_t size)
{
struct hsmp_message msg = { 0 };
int ret;
+ lockdep_assert_held(&hsmp_sock_rwsem);
+
if (!sock || !buf)
return -EINVAL;
@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
return -ENOMEM;
}
- /* Do not support lseek(), also don't allow more than the size of metric table */
- if (size != sizeof(struct hsmp_metric_table)) {
+ if (size != sock->metric_tbl_size) {
dev_err(sock->dev, "Wrong buffer size\n");
return -EINVAL;
}
@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
msg.msg_id = HSMP_GET_METRIC_TABLE;
msg.sock_ind = sock->sock_ind;
- ret = hsmp_send_message(&msg);
+ /*
+ * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared
+ * metric DRAM region, which is then copied out below. Hold the
+ * per-socket lock across the fill-and-copy so concurrent readers of the
+ * same socket cannot return a torn snapshot.
+ */
+ guard(mutex)(&sock->metric_read_lock);
+
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
memcpy_fromio(buf, sock->metric_tbl_addr, size);
return size;
}
+
+ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
+{
+ guard(rwsem_read)(&hsmp_sock_rwsem);
+
+ return hsmp_metric_tbl_read_locked(sock, buf, size);
+}
EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP");
+void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev)
+{
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++)
+ mutex_init(&pdev->sock[i].metric_read_lock);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP");
+
+void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev)
+{
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++)
+ mutex_destroy(&pdev->sock[i].metric_read_lock);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP");
+
+void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev)
+{
+ struct hsmp_socket *sock;
+ u16 i;
+
+ for (i = 0; i < pdev->num_sockets; i++) {
+ sock = &pdev->sock[i];
+ if (sock->metric_tbl_addr) {
+ iounmap(sock->metric_tbl_addr);
+ sock->metric_tbl_addr = NULL;
+ }
+ sock->metric_tbl_size = 0;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP");
+
int hsmp_get_tbl_dram_base(u16 sock_ind)
{
struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind];
struct hsmp_message msg = { 0 };
phys_addr_t dram_addr;
+ size_t tbl_size;
int ret;
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind)
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
return -ENOMEM;
}
- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
- sizeof(struct hsmp_metric_table));
+ /*
+ * The ACPI socket array is shared across sockets and outlives a
+ * per-socket unbind, so metric_tbl_addr may hold a mapping from an
+ * earlier bind of this socket. Unmap it before remapping so an
+ * unbind/rebind cycle does not leak a metric-table mapping. This runs
+ * during probe before the metric sysfs attribute is exposed, so no
+ * reader can be using it.
+ */
+ if (sock->metric_tbl_addr) {
+ iounmap(sock->metric_tbl_addr);
+ sock->metric_tbl_addr = NULL;
+ }
+ sock->metric_tbl_size = 0;
+
+ /* SMU returns table size from Family 1Ah Model 50h and forward */
+ if (msg.args[2])
+ tbl_size = msg.args[2];
+ else
+ tbl_size = sizeof(struct hsmp_metric_table);
+
+ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size);
if (!sock->metric_tbl_addr) {
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
return -ENOMEM;
}
+ sock->metric_tbl_size = tbl_size;
+
return 0;
}
EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP");
@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind)
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
- ret = hsmp_send_message(&msg);
+ ret = hsmp_send_message_locked(&msg);
if (!ret)
hsmp_pdev.proto_ver = msg.args[0];
@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev)
hsmp_pdev.mdev.name = HSMP_CDEV_NAME;
hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR;
hsmp_pdev.mdev.fops = &hsmp_fops;
+ /*
+ * The caller chooses the parent. The platform driver has a single
+ * device whose lifetime matches /dev/hsmp and parents it there. The
+ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by
+ * per-socket devices that can be unbound individually and out of order,
+ * so parenting it to one would leave it attached to an already-removed
+ * device.
+ */
hsmp_pdev.mdev.parent = dev;
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
hsmp_pdev.mdev.mode = 0644;
@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP");
void hsmp_misc_deregister(void)
{
misc_deregister(&hsmp_pdev.mdev);
+ hsmp_pdev.mdev.this_device = NULL;
}
EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP");
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -15,9 +15,12 @@
#include <linux/hwmon.h>
#include <linux/kconfig.h>
#include <linux/miscdevice.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
+#include <linux/types.h>
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
@@ -27,7 +30,7 @@
#define HSMP_DEVNODE_NAME "hsmp"
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
-#define DRIVER_VERSION "2.5"
+#define DRIVER_VERSION "2.6"
struct hsmp_mbaddr_info {
u32 base_addr;
@@ -41,8 +44,12 @@ struct hsmp_socket {
struct bin_attribute hsmp_attr;
struct hsmp_mbaddr_info mbinfo;
void __iomem *metric_tbl_addr;
+ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */
+ size_t metric_tbl_size;
void __iomem *virt_base_addr;
struct semaphore hsmp_sem;
+ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */
+ struct mutex metric_read_lock;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct device *dev;
u16 sock_ind;
@@ -54,7 +61,6 @@ struct hsmp_plat_device {
struct hsmp_socket *sock;
u32 proto_ver;
u16 num_sockets;
- bool is_probed;
};
int hsmp_cache_proto_ver(u16 sock_ind);
@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg);
void hsmp_misc_deregister(void);
int hsmp_misc_register(struct device *dev);
int hsmp_get_tbl_dram_base(u16 sock_ind);
+void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev);
+void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev);
+void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev);
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
struct hsmp_plat_device *get_hsmp_pdev(void);
#if IS_ENABLED(CONFIG_HWMON)
@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind);
static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; }
#endif
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args);
+
+/*
+ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and
+ * remove take it for write to bring sockets up and tear them down.
+ */
+extern struct rw_semaphore hsmp_sock_rwsem;
#endif /* HSMP_H */
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -13,12 +13,14 @@
#include <linux/acpi.h>
#include <linux/build_bug.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
#include <linux/sysfs.h>
#include <asm/amd/node.h>
@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev)
return 0;
}
+/*
+ * The socket array is devm-managed and freed by the driver core, but the
+ * metric-table DRAM regions are mapped with plain ioremap() during probe and
+ * the per-socket mutexes need an explicit mutex_destroy(), neither of which
+ * devres covers.
+ *
+ * Take the data-plane rwsem for write to drain any in-flight
+ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop
+ * the global socket pointer, all before devres frees the array. Registered as
+ * a devres action so it runs on both remove and probe failure.
+ */
+static void hsmp_pltdrv_release(void *data)
+{
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+ hsmp_unmap_metric_tbls(hsmp_pdev);
+ hsmp_destroy_metric_read_locks(hsmp_pdev);
+ hsmp_pdev->sock = NULL;
+}
+
static int hsmp_pltdrv_probe(struct platform_device *pdev)
{
int ret;
@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
if (!hsmp_pdev->sock)
return -ENOMEM;
- ret = init_platform_device(&pdev->dev);
+ hsmp_init_metric_read_locks(hsmp_pdev);
+
+ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL);
+ if (ret)
+ return ret;
+
+ /*
+ * init_platform_device() runs the mailbox handshake via the probe-only
+ * senders, which issue messages through hsmp_send_message_locked() and
+ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's
+ * role as a socket bring-up path. The lock is not held across
+ * devm_add_action_or_reset() above so the release action, which also
+ * takes it for write, does not deadlock if that registration fails.
+ */
+ scoped_guard(rwsem_write, &hsmp_sock_rwsem)
+ ret = init_platform_device(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
return ret;
diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c
--- a/drivers/platform/x86/amd/pmc/mp1_stb.c
+++ b/drivers/platform/x86/amd/pmc/mp1_stb.c
@@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
if (!enable_stb)
return 0;
- if (amd_is_stb_supported(dev)) {
- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
- &amd_stb_debugfs_fops_v2);
- } else {
+ if (!amd_is_stb_supported(dev)) {
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_stb_debugfs_fops);
return 0;
@@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
}
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
- if (!dev->stb_virt_addr)
+ if (!dev->stb_virt_addr) {
ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Only expose stb_read once the buffer is mapped; otherwise a read
+ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no
+ * longer aborts probe.
+ */
+ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
+ &amd_stb_debugfs_fops_v2);
out:
/* Restore the default message port for subsequent SMU operations */
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "20XK"),
}
},
+ {
+ .ident = "T14 Gen2 AMD",
+ .driver_data = &quirk_s2idle_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"),
+ }
+ },
{
.ident = "T14 Gen1 AMD",
.driver_data = &quirk_s2idle_spurious_8042,
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -939,19 +939,22 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
amd_pmc_dbgfs_register(dev);
+
+ /*
+ * STB is an optional debug facility (enable_stb); a failure to set it
+ * up must not stop the rest of the driver - most importantly the s0i3
+ * LPS0 handler - from working, so treat it as non-fatal.
+ */
err = amd_stb_s2d_init(dev);
if (err)
- goto err_dbgfs_unregister;
+ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n",
+ err);
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
amd_mp2_stb_init(dev);
pm_report_max_hw_sleep(U64_MAX);
return 0;
-err_dbgfs_unregister:
- amd_pmc_dbgfs_unregister(dev);
- if (IS_ENABLED(CONFIG_SUSPEND))
- acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops);
err_pci_dev_put:
pci_dev_put(rdev);
return err;
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
--- 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
--- a/drivers/platform/x86/amd/pmf/Makefile
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -7,4 +7,6 @@
obj-$(CONFIG_AMD_PMF) += amd-pmf.o
amd-pmf-y := core.o acpi.o sps.o \
auto-mode.o cnqf.o \
- tee-if.o spc.o
+ tee-if.o spc.o metrics.o
+# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled
+amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -11,13 +11,13 @@
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
+#include <linux/dev_printk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
-#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
@@ -26,6 +26,13 @@
#define AMD_PMF_REGISTER_RESPONSE 0xA78
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
+/* PMF-SMU communication registers for 1AH_M80H */
+#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04
+#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08
+#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C
+#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC
+#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0
+
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMF_MAPPING_SIZE 0x01000
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
@@ -48,7 +55,7 @@
#define DELAY_MAX_US 3000
/* override Metrics Table sample size time (in ms) */
-static int metrics_table_loop_ms = 1000;
+int metrics_table_loop_ms = 1000;
module_param(metrics_table_loop_ms, int, 0644);
MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)");
@@ -61,7 +68,15 @@ static bool smart_pc_support = true;
module_param(smart_pc_support, bool, 0444);
MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
-static struct device *pmf_device;
+static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ return true;
+ default:
+ return false;
+ }
+}
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void)
return POWER_SOURCE_DC;
}
-static void amd_pmf_get_metrics(struct work_struct *work)
-{
- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
- ktime_t time_elapsed_ms;
- int socket_power;
-
- guard(mutex)(&dev->update_mutex);
-
- /* Transfer table contents */
- memset(dev->buf, 0, sizeof(dev->m_table));
- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
-
- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
- /* Calculate the avg SoC power consumption */
- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
-
- if (dev->amt_enabled) {
- /* Apply the Auto Mode transition */
- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
- }
-
- if (dev->cnqf_enabled) {
- /* Apply the CnQF transition */
- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
- }
-
- dev->start_time = ktime_to_ms(ktime_get());
- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
-}
-
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
{
return ioread32(dev->regbase + reg_offset);
@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
{
u32 value;
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
+ if (amd_pmf_supports_accumulator_metrics(dev)) {
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value);
+ }
- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
+ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
guard(mutex)(&dev->lock);
/* Wait until we get a valid response */
- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
}
/* Write zero to response register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
+ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0);
/* Write argument into argument register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
+ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg);
/* Write message ID to message ID register */
- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
+ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message);
/* Wait until we get a valid response */
- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
if (get) {
/* PMFW may take longer time to return back the data */
usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
+ if (amd_pmf_supports_accumulator_metrics(dev) &&
+ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) {
+ dev->dram_addr.hi = amd_pmf_reg_read(dev,
+ dev->smu_regs->arg_reg[1]);
+ dev->dram_addr.size = amd_pmf_reg_read(dev,
+ dev->smu_regs->arg_reg[2]);
+ }
}
break;
case AMD_PMF_RESULT_CMD_REJECT_BUSY:
@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
return rc;
}
-static const struct pci_device_id pmf_pci_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
- { }
+/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */
+static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = {
+ .msg_reg = AMD_PMF_REGISTER_MESSAGE,
+ .resp_reg = AMD_PMF_REGISTER_RESPONSE,
+ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 },
};
-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
-{
- u64 phys_addr;
- u32 hi, low;
+/* 1AH_M80H uses an extended mailbox with three argument registers */
+static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = {
+ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2,
+ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2,
+ .arg_reg = {
+ AMD_PMF_REGISTER_ARGUMENT0_V2,
+ AMD_PMF_REGISTER_ARGUMENT1_V2,
+ AMD_PMF_REGISTER_ARGUMENT2_V2,
+ },
+};
- /* Get Metrics Table Address */
- if (alloc_buffer) {
- switch (dev->cpu_id) {
- case AMD_CPU_ID_PS:
- case AMD_CPU_ID_RMB:
- dev->mtable_size = sizeof(dev->m_table);
- break;
- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
- dev->mtable_size = sizeof(dev->m_table_v2);
- break;
- default:
- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
- }
-
- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
- if (!dev->buf)
- return -ENOMEM;
- }
-
- phys_addr = virt_to_phys(dev->buf);
- hi = phys_addr >> 32;
- low = phys_addr & GENMASK(31, 0);
-
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
-
- return 0;
-}
-
-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
-{
- int ret;
-
- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
-
- ret = amd_pmf_set_dram_addr(dev, true);
- if (ret)
- return ret;
-
- /*
- * Start collecting the metrics data after a small delay
- * or else, we might end up getting stale values from PMFW.
- */
- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
-
- return 0;
-}
-
-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
-{
- switch (pdev->cpu_id) {
- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
- return 0;
- default:
- return -EOPNOTSUPP;
- }
-}
-
-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
-{
- int ret, i;
-
- guard(mutex)(&dev->metrics_mutex);
-
- ret = is_npu_metrics_supported(dev);
- if (ret)
- return ret;
-
- ret = amd_pmf_set_dram_addr(dev, true);
- if (ret)
- return ret;
-
- memset(dev->buf, 0, dev->mtable_size);
-
- /* Send SMU command to get NPU metrics */
- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
- if (ret) {
- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
- return ret;
- }
-
- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
-
- data->npuclk_freq = dev->m_table_v2.npuclk_freq;
- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
- data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
- data->npu_power = dev->m_table_v2.npu_power;
- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
- data->npu_reads = dev->m_table_v2.npu_reads;
- data->npu_writes = dev->m_table_v2.npu_writes;
-
- return 0;
-}
-
-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
-{
- struct amd_pmf_dev *pdev;
-
- if (!info)
- return -EINVAL;
-
- if (!pmf_device)
- return -ENODEV;
-
- pdev = dev_get_drvdata(pmf_device);
- if (!pdev)
- return -ENODEV;
-
- return amd_pmf_get_smu_metrics(pdev, info);
-}
-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
+static const struct pci_device_id pmf_pci_ids[] = {
+ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) },
+ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) },
+ { }
+};
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
{
@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
}
}
+static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev)
+{
+ const struct pci_device_id *id;
+
+ id = pci_match_id(pmf_pci_ids, rdev);
+ if (!id)
+ return -ENODEV;
+
+ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data;
+
+ return 0;
+}
+
static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0100", 0x100},
{"AMDI0102", 0},
@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0105", 0},
{"AMDI0107", 0},
{"AMDI0108", 0},
+ {"AMDI0109", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (err)
return err;
+ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */
+ err = amd_pmf_get_smu_mb_offset(dev, rdev);
+ if (err)
+ return err;
+
+ if (amd_pmf_supports_accumulator_metrics(dev)) {
+ err = amd_pmf_get_tbl_dram_addr(dev);
+ if (err)
+ return err;
+ }
+
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
- pmf_device = dev->dev;
+ amd_pmf_set_device(dev->dev);
+
+ err = amd_pmf_cdev_register(dev);
+ if (err)
+ dev_warn(dev->dev, "failed to register util interface: %d\n", err);
dev_info(dev->dev, "registered PMF device successfully\n");
@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev)
{
struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
+ amd_pmf_cdev_unregister();
amd_pmf_deinit_features(dev);
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD);
diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c
new file mode 100644
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/metrics.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Driver - Metrics Support
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Patil Rajesh Reddy <Patil.Reddy@amd.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/io.h>
+#include <linux/ktime.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/wordpart.h>
+#include <linux/workqueue.h>
+
+#include "pmf.h"
+
+static struct device *pmf_device;
+
+static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc)
+{
+ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT;
+
+ return min_t(u16, val, U16_MAX);
+}
+
+static u16 amd_pmf_q10_acc_to_int(u64 avg_acc)
+{
+ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024);
+
+ return min_t(u16, val, U16_MAX);
+}
+
+static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter,
+ u64 curr_value, u64 prev_value)
+{
+ u32 counter_diff;
+ u64 val_diff;
+
+ /* Check for counter reset */
+ if (curr_counter <= prev_counter)
+ return 0;
+
+ counter_diff = curr_counter - prev_counter;
+
+ if (curr_value < prev_value)
+ return 0;
+
+ val_diff = curr_value - prev_value;
+
+ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff);
+}
+
+int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD,
+ ARG_NONE, &dev->dram_addr.lo);
+ if (ret) {
+ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret);
+ return ret;
+ }
+
+ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo;
+ dev->mtable_size = dev->dram_addr.size;
+
+ if (dev->mtable_size != sizeof(dev->mtable_v3)) {
+ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n",
+ dev->dram_addr.size, sizeof(dev->mtable_v3));
+ return -EINVAL;
+ }
+
+ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size);
+ if (!dev->metrics_table_virt) {
+ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n");
+ dev->metrics_table_phys = 0;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ if (!dev->metrics_table_virt) {
+ dev_err(dev->dev, "Metrics DRAM not mapped\n");
+ return -EINVAL;
+ }
+
+ /* Send command to PMFW to update metrics table log sample */
+ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL);
+ if (ret) {
+ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void amd_pmf_get_metrics(struct work_struct *work)
+{
+ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
+ ktime_t time_elapsed_ms;
+ int socket_power;
+
+ guard(mutex)(&dev->update_mutex);
+
+ /* Transfer table contents */
+ memset(dev->buf, 0, sizeof(dev->m_table));
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
+
+ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
+ /* Calculate the avg SoC power consumption */
+ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
+
+ if (dev->amt_enabled) {
+ /* Apply the Auto Mode transition */
+ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
+ }
+
+ if (dev->cnqf_enabled) {
+ /* Apply the CnQF transition */
+ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
+ }
+
+ dev->start_time = ktime_to_ms(ktime_get());
+ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
+}
+
+int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
+{
+ u64 phys_addr;
+ u32 hi, low;
+
+ /* Get Metrics Table Address */
+ if (alloc_buffer) {
+ switch (dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ case AMD_CPU_ID_RMB:
+ dev->mtable_size = sizeof(dev->m_table);
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ dev->mtable_size = sizeof(dev->m_table_v2);
+ break;
+ default:
+ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id);
+ }
+
+ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
+ if (!dev->buf)
+ return -ENOMEM;
+ }
+
+ phys_addr = virt_to_phys(dev->buf);
+ hi = upper_32_bits(phys_addr);
+ low = lower_32_bits(phys_addr);
+
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
+
+ return 0;
+}
+
+int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
+
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ /*
+ * Start collecting the metrics data after a small delay
+ * or else, we might end up getting stale values from PMFW.
+ */
+ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
+
+ return 0;
+}
+
+void amd_pmf_set_device(struct device *p_device)
+{
+ pmf_device = p_device;
+}
+
+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev,
+ struct amd_pmf_npu_metrics *data)
+{
+ struct amd_pmf_metrics_iod *curr, *prev;
+ u64 val;
+ int i;
+
+ curr = &dev->mtable_v3.iod;
+ prev = &dev->prev_metrics.iod;
+
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_temp_acc, prev->npu_temp_acc);
+ data->npu_temp = amd_pmf_q10_acc_to_int(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_power_acc, prev->npu_power_acc);
+ data->npu_power = amd_pmf_q10_acc_to_mW(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npuhclk_freq_eff_acc,
+ prev->npuhclk_freq_eff_acc);
+ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val);
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->aieclk_freq_eff_acc,
+ prev->aieclk_freq_eff_acc);
+ data->npuclk_freq = amd_pmf_q10_acc_to_int(val);
+
+ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) {
+ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
+ curr->npu_busy_acc[i],
+ prev->npu_busy_acc[i]);
+ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val);
+ }
+}
+
+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
+{
+ int ret, i;
+
+ guard(mutex)(&dev->metrics_mutex);
+
+ ret = is_npu_metrics_supported(dev);
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(*data));
+
+ switch (dev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ memset(dev->buf, 0, dev->mtable_size);
+
+ /* Send SMU command to get NPU metrics */
+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ if (ret) {
+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+
+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
+ data->npu_power = dev->m_table_v2.npu_power;
+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
+ data->npu_reads = dev->m_table_v2.npu_reads;
+ data->npu_writes = dev->m_table_v2.npu_writes;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
+ ret = amd_pmf_get_metrics_table_log_sample(dev);
+ if (ret)
+ return ret;
+
+ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3));
+
+ /*
+ * Ignore the first sample, as previous metrics is uninitialized (zero)
+ * Metrics are calculated as:
+ * metrics = current_metrics - previous_metrics
+ * Skipping the initial sample ensures accurate delta calculations.
+ */
+ if (!dev->npu_metrics_have_prev) {
+ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
+ dev->npu_metrics_have_prev = true;
+ return 0;
+ }
+
+ amd_pmf_calculate_acc_npu_metrics(dev, data);
+ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
+ break;
+ }
+
+ return 0;
+}
+
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
+{
+ struct amd_pmf_dev *pdev;
+
+ if (!info)
+ return -EINVAL;
+
+ if (!pmf_device)
+ return -ENODEV;
+
+ pdev = dev_get_drvdata(pmf_device);
+ if (!pdev)
+ return -ENODEV;
+
+ return amd_pmf_get_smu_metrics(pdev, info);
+}
+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -14,10 +14,13 @@
#include <linux/acpi.h>
#include <linux/amd-pmf-io.h>
#include <linux/circ_buf.h>
+#include <linux/compiler_attributes.h>
+#include <linux/compiler_types.h>
#include <linux/input.h>
#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
+#include <linux/types.h>
#define POLICY_BUF_MAX_SZ 0x4b000
#define POLICY_SIGN_COOKIE 0x31535024
@@ -28,6 +31,11 @@
#define AMD_CPU_ID_PS 0x14e8
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
+#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b
+
+/* Aliases required by PCI_DEVICE_DATA() macro naming convention */
+#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB
+#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS
struct cookie_header {
u32 sign;
@@ -70,6 +78,10 @@ struct cookie_header {
#define SET_PMF_PPT 0x25
#define SET_PMF_PPT_APU_ONLY 0x26
+/* Message IDs for 1AH_M80H platform */
+#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E
+#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F
+
/* OS slider update notification */
#define DC_BEST_PERF 0
#define DC_BETTER_PERF 1
@@ -130,6 +142,14 @@ struct cookie_header {
#define GET_CMD true
#define METRICS_TABLE_ID 7
+#define BIOS_OUTPUT_MAX 10
+
+extern int metrics_table_loop_ms;
+
+#define AMD_PMF_METRIC_CORE_TYPE_MAX 4
+#define AMD_PMF_METRIC_CCX_MAX 4
+#define AMD_PMF_NUM_CLK_DPM_LEVELS 8
+#define AMD_PMF_NUM_MAX_CORES 12
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
@@ -242,6 +262,199 @@ struct apmf_fan_idx {
u32 fan_ctl_idx;
} __packed;
+struct amd_pmf_metrics_iod {
+ u32 counter_acc;
+ /* Set Voltage */
+ u64 vddcr_set_voltage;
+ u64 vddcr_soc_set_voltage;
+ u64 vddcr_npu_set_voltage;
+ u64 vddcr_lp_set_voltage;
+ u64 vddcr_gfx_set_voltage;
+ u64 vdd_misc_set_voltage;
+ /* Telemetry Voltages */
+ u64 vddcr_telemetry_voltage;
+ u64 vddcr_soc_telemetry_voltage;
+ u64 vddcr_npu_telemetry_voltage;
+ u64 vddcr_lp_telemetry_voltage;
+ u64 vddcr_gfx_telemetry_voltage;
+ u64 vdd_misc_telemetry_voltage;
+ /* Telemetry Powers */
+ u64 vddcr_telemetry_power;
+ u64 vddcr_soc_telemetry_power;
+ u64 vddcr_npu_telemetry_power;
+ u64 vddcr_lp_telemetry_power;
+ u64 vddcr_gfx_telemetry_power;
+ u64 vdd_misc_telemetry_power;
+ /* Throttlers - Fast PPT */
+ u32 fppt_fused_limit;
+ u32 fppt_max_irm_limit;
+ u32 fppt_max_pbo_limit;
+ u32 fppt_limit;
+ u64 fppt_value_acc;
+ u32 fppt_residency_acc;
+ /* Throttlers - Slow PPT */
+ u32 sppt_fused_limit;
+ u32 sppt_max_irm_limit;
+ u32 sppt_max_pbo_limit;
+ u32 sppt_limit;
+ u64 sppt_value_acc;
+ u32 sppt_residency_acc;
+ /* Throttlers - STAPM */
+ u32 spl_fused_limit;
+ u32 spl_max_irm_limit;
+ u32 spl_max_pbo_limit;
+ u32 spl_limit;
+ u64 spl_value_acc;
+ u32 spl_residency_acc;
+ /* Throttlers - TDC VDDCR */
+ u32 tdc_vddcr_fused_limit;
+ u32 tdc_vddcr_max_irm_limit;
+ u32 tdc_vddcr_max_pbo_limit;
+ u32 tdc_vddcr_limit;
+ u64 tdc_vddcr_value_acc;
+ u32 tdc_vddcr_residency_acc;
+ /* Throttlers - TDC VDDCR_SOC */
+ u32 tdc_vddcr_soc_fused_limit;
+ u32 tdc_vddcr_soc_max_irm_limit;
+ u32 tdc_vddcr_soc_max_pbo_limit;
+ u32 tdc_vddcr_soc_limit;
+ u64 tdc_vddcr_soc_value_acc;
+ u32 tdc_vddcr_soc_residency_acc;
+ /* Throttlers - TDC VDDCR_NPU */
+ u32 tdc_vddcr_npu_fused_limit;
+ u32 tdc_vddcr_npu_max_irm_limit;
+ u32 tdc_vddcr_npu_max_pbo_limit;
+ u32 tdc_vddcr_npu_limit;
+ u64 tdc_vddcr_npu_value_acc;
+ u32 tdc_vddcr_npu_residency_acc;
+ /* Throttlers - TDC VDDCR_LP */
+ u32 tdc_vddcr_lp_fused_limit;
+ u32 tdc_vddcr_lp_max_irm_limit;
+ u32 tdc_vddcr_lp_max_pbo_limit;
+ u32 tdc_vddcr_lp_limit;
+ u64 tdc_vddcr_lp_value_acc;
+ u32 tdc_vddcr_lp_residency_acc;
+ /* Throttlers - TDC VDDCR_GFX */
+ u32 tdc_vddcr_gfx_fused_limit;
+ u32 tdc_vddcr_gfx_max_irm_limit;
+ u32 tdc_vddcr_gfx_max_pbo_limit;
+ u32 tdc_vddcr_gfx_limit;
+ u64 tdc_vddcr_gfx_value_acc;
+ u32 tdc_vddcr_gfx_residency_acc;
+ /* Throttlers - EDC VDDCR */
+ u32 edc_vddcr_fused_limit;
+ u32 edc_vddcr_max_irm_limit;
+ u32 edc_vddcr_max_pbo_limit;
+ u32 edc_vddcr_limit;
+ /* Throttlers - Thermal */
+ u32 thm_fused_limit;
+ u32 thm_limit;
+ u64 thm_value_acc;
+ u32 thm_residency_acc;
+ u32 prochot_residency_acc;
+ u64 gfx_temp_acc;
+ u64 soc_temp_acc;
+ u32 p3t_fused_limit;
+ u64 p3t_value_acc;
+ /* Power */
+ u64 system_power_acc;
+ u64 apu_power_acc;
+ u64 dgpu_power_acc;
+ u64 npu_power_acc;
+ /* Frequencies */
+ u64 fclk_freq_eff_acc;
+ u64 memclk_freq_eff_acc;
+ u64 lclk_freq_eff_acc;
+ u64 gfxclk_freq_eff_acc;
+ u64 socclk_freq_eff_acc;
+ u64 vclk_freq_eff_acc;
+ u64 vpeclk_freq_eff_acc;
+ u64 aieclk_freq_eff_acc;
+ u64 npuhclk_freq_eff_acc;
+ /* Bandwidth */
+ u64 dram_read_bandwidth;
+ u64 dram_write_bandwidth;
+ /* Activity Monitors */
+ u64 gfx_busy_acc;
+ u64 vcn_busy_acc;
+ u64 npu_busy_acc[3];
+ /* STT Limits */
+ u32 stt_min_limit;
+ u64 stt_apu_hotspot_temp_acc;
+ u64 stt_hs2_hotspot_temp_acc;
+ u32 stt_apu_temp_limit;
+ u64 stt_apu_skin_temp_acc;
+ /* Residencies */
+ u64 cpuoff_residency_ccx0;
+ u64 cpuoff_residency_ccx1;
+ u64 cpuoff_residency_ccx2;
+ u64 cpuoff_residency_ccx3;
+ /* DF-pstates */
+ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ /* System */
+ u8 gfx_disabled;
+ u8 spare2[3];
+ u32 gfxclk_fmax;
+ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
+ /* Overclock Capable */
+ u8 cpu_precise_and_direct_oc_capable;
+ u8 gfx_precise_and_direct_oc_capable;
+ u8 pbo_basic_oc_capable;
+ u8 pbo_advanced_oc_capable;
+ u8 pbo_nitro_oc_capable;
+ u8 memory_and_fabric_oc_capable;
+ u8 misc_oc_capable;
+ u8 extreme_cold_oc_capable;
+ u8 down_config_control_capable;
+ u8 spare0[3];
+ /* Overclock Status */
+ u32 fit_limit_scalar;
+ u8 ln2_enabled;
+ u8 cpu_precise_and_direct_oc_enabled;
+ u8 gfx_precise_and_direct_oc_enabled;
+ u8 spare1[2];
+ /* Voltage Guardband in PSM count */
+ s8 psm_guardband[5][5][3];
+ s32 core_power_limit_offset;
+ u32 max_freq_offset[5];
+ u64 npu_temp_acc;
+ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS];
+ u32 cclk_fboost;
+ /* PMF */
+ u32 pmf_fast_apu_ppt_limit;
+ u64 pmf_fast_apu_ppt_value_acc;
+ u32 pmf_fast_apu_ppt_residency_acc;
+ u32 pmf_slow_apu_ppt_limit;
+ u64 pmf_slow_apu_ppt_value_acc;
+ u32 pmf_slow_apu_ppt_residency_acc;
+ u32 pmf_fast_spm_limit;
+ u64 pmf_fast_spm_value_acc;
+ u32 pmf_fast_spm_residency_acc;
+ u32 pmf_slow_spm_limit;
+ u64 pmf_slow_spm_value_acc;
+ u32 pmf_slow_spm_residency_acc;
+ u32 spare3[5];
+} __packed __aligned(4);
+
+struct amd_pmf_metrics_ccx {
+ u64 core_c0[AMD_PMF_NUM_MAX_CORES];
+ u64 core_cc6[AMD_PMF_NUM_MAX_CORES];
+ u64 core_freq[AMD_PMF_NUM_MAX_CORES];
+ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES];
+ u64 core_temp[AMD_PMF_NUM_MAX_CORES];
+ u64 core_power[AMD_PMF_NUM_MAX_CORES];
+} __packed __aligned(4);
+
+struct amd_pmf_metrics_v3 {
+ struct amd_pmf_metrics_iod iod;
+ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX];
+} __packed __aligned(4);
+
struct smu_pmf_metrics_v2 {
u16 core_frequency[16]; /* MHz */
u16 core_power[16]; /* mW */
@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer {
int tail;
};
+/* SoC-specific SMU mailbox register offsets */
+struct amd_pmf_smu_regs {
+ u32 msg_reg;
+ u32 resp_reg;
+ u32 arg_reg[3];
+};
+
+struct amd_pmf_arg_data {
+ u32 lo;
+ u32 hi;
+ u32 size;
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -442,6 +668,14 @@ struct amd_pmf_dev {
struct pmf_cbi_ring_buffer cbi_buf;
struct mutex cbi_mutex; /* Protects ring buffer access */
struct mutex metrics_mutex;
+ u32 bios_output[BIOS_OUTPUT_MAX];
+ const struct amd_pmf_smu_regs *smu_regs;
+ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */
+ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */
+ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */
+ struct amd_pmf_arg_data dram_addr;
+ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */
+ bool npu_metrics_have_prev;
};
struct apmf_sps_prop_granular_v2 {
@@ -680,14 +914,6 @@ enum system_state {
SYSTEM_STATE_MAX,
};
-enum ta_slider {
- TA_BEST_BATTERY,
- TA_BETTER_BATTERY,
- TA_BETTER_PERFORMANCE,
- TA_BEST_PERFORMANCE,
- TA_MAX,
-};
-
struct amd_pmf_pb_bitmap {
const char *name;
u32 bit_mask;
@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
{"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
};
-enum platform_type {
- PTYPE_UNKNOWN = 0,
- LID_CLOSE,
- CLAMSHELL,
- FLAT,
- TENT,
- STAND,
- TABLET,
- BOOK,
- PRESENTATION,
- PULL_FWD,
- PTYPE_INVALID = 0xf,
-};
-
/* Command ids for TA communication */
enum ta_pmf_command {
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
@@ -871,6 +1083,10 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
u32 fixp_q88_fromint(u32 val);
int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
+void amd_pmf_set_device(struct device *p_device);
+
+/* Metrics layer */
+int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
+u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index);
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
+/* Util Layer */
+#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT)
+int amd_pmf_cdev_register(struct amd_pmf_dev *dev);
+void amd_pmf_cdev_unregister(void);
+#else
+static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; }
+static inline void amd_pmf_cdev_unregister(void) {}
+#endif
+
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
--- 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
--- 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
--- /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
--- 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 @@ static int asus_fw_attr_add(void)
/* 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
--- a/drivers/platform/x86/asus-armoury.h
+++ b/drivers/platform/x86/asus-armoury.h
@@ -369,6 +369,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"),
@@ -754,6 +786,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"),
@@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = {
.requires_fan_curve = true,
},
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "FX607VU"),
@@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = {
.requires_fan_curve = true,
},
},
+ {
+ .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"),
@@ -2322,6 +2443,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"),
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -42,8 +42,6 @@
#define ASUS_LAPTOP_VERSION "0.42"
#define ASUS_LAPTOP_NAME "Asus Laptop Support"
-#define ASUS_LAPTOP_CLASS "hotkey"
-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey"
#define ASUS_LAPTOP_FILE KBUILD_MODNAME
#define ASUS_LAPTOP_PREFIX "\\_SB.ATKD."
@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data)
/* TODO Find a better way to handle events count. */
count = asus->event_count[event % 128]++;
- acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
- dev_name(&asus->device->dev), event,
- count);
+ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev),
+ event, count);
if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
event = ATKD_BRNUP;
@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev)
if (!asus)
return -ENOMEM;
asus->handle = device->handle;
- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
asus->device = device;
asus_dmi_check();
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */
{ KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */
{ KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */
{ KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
{ KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
{ KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
--- 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)
{
diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c
--- a/drivers/platform/x86/dell/dell-smbios-base.c
+++ b/drivers/platform/x86/dell/dell-smbios-base.c
@@ -40,7 +40,7 @@ struct smbios_device {
struct list_head list;
struct device *device;
int priority;
- int (*call_fn)(struct calling_interface_buffer *arg);
+ smbios_callback_fn_t call_fn;
};
struct smbios_call {
@@ -146,7 +146,7 @@ int dell_smbios_error(int value)
}
EXPORT_SYMBOL_GPL(dell_smbios_error);
-int dell_smbios_register_device(struct device *d, int priority, void *call_fn)
+int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn)
{
struct smbios_device *priv;
@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer)
goto out_smbios_call;
}
- ret = selected->call_fn(buffer);
+ ret = selected->call_fn(selected->device, buffer);
out_smbios_call:
mutex_unlock(&smbios_mutex);
diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c
--- a/drivers/platform/x86/dell/dell-smbios-smm.c
+++ b/drivers/platform/x86/dell/dell-smbios-smm.c
@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy)
}
}
-static int dell_smbios_smm_call(struct calling_interface_buffer *input)
+static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input)
{
struct smi_cmd command;
size_t size;
@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input)
}
/* When enabled this indicates that SMM won't work */
-static bool test_wsmt_enabled(void)
+static bool test_wsmt_enabled(struct device *dev)
{
struct calling_interface_token *wsmt;
@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void)
memset(buffer, 0, sizeof(struct calling_interface_buffer));
buffer->input[0] = wsmt->location;
buffer->output[0] = 99;
- dell_smbios_smm_call(buffer);
+ dell_smbios_smm_call(dev, buffer);
if (buffer->output[0] == 99)
return true;
@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void)
dmi_walk(find_cmd_address, NULL);
- if (test_wsmt_enabled()) {
+ if (test_wsmt_enabled(&platform_device->dev)) {
pr_debug("Disabling due to WSMT enabled\n");
ret = -ENODEV;
goto fail_wsmt;
diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c
--- a/drivers/platform/x86/dell/dell-smbios-wmi.c
+++ b/drivers/platform/x86/dell/dell-smbios-wmi.c
@@ -6,21 +6,20 @@
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/fs.h>
-#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/uaccess.h>
#include <linux/wmi.h>
#include <uapi/linux/wmi.h>
#include "dell-smbios.h"
#include "dell-wmi-descriptor.h"
-static DEFINE_MUTEX(call_mutex);
-static DEFINE_MUTEX(list_mutex);
static int wmi_supported;
struct misc_bios_flags_structure {
@@ -32,21 +31,16 @@ struct misc_bios_flags_structure {
#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
struct wmi_smbios_priv {
+ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */
struct dell_wmi_smbios_buffer *buf;
- struct list_head list;
struct wmi_device *wdev;
struct device *child;
u64 req_buf_size;
struct miscdevice char_dev;
};
-static LIST_HEAD(wmi_list);
-static inline struct wmi_smbios_priv *get_first_smbios_priv(void)
-{
- return list_first_entry_or_null(&wmi_list,
- struct wmi_smbios_priv,
- list);
-}
+static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */
+static struct wmi_smbios_priv *chardev_priv;
static int run_smbios_call(struct wmi_device *wdev)
{
@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev)
return 0;
}
-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
+static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer)
{
- struct wmi_smbios_priv *priv;
+ struct wmi_smbios_priv *priv = dev_get_drvdata(dev);
size_t difference;
size_t size;
int ret;
- mutex_lock(&call_mutex);
- priv = get_first_smbios_priv();
- if (!priv) {
- ret = -ENODEV;
- goto out_wmi_call;
- }
-
size = sizeof(struct calling_interface_buffer);
difference = priv->req_buf_size - sizeof(u64) - size;
+ guard(mutex)(&priv->call_lock);
+
memset(&priv->buf->ext, 0, difference);
memcpy(&priv->buf->std, buffer, size);
ret = run_smbios_call(priv->wdev);
memcpy(buffer, &priv->buf->std, size);
-out_wmi_call:
- mutex_unlock(&call_mutex);
return ret;
}
-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp)
-{
- struct wmi_smbios_priv *priv;
-
- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev);
- filp->private_data = priv;
-
- return nonseekable_open(inode, filp);
-}
-
static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length,
loff_t *offset)
{
- struct wmi_smbios_priv *priv = filp->private_data;
+ guard(rwsem_read)(&chardev_lock);
- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size,
- sizeof(priv->req_buf_size));
+ if (!chardev_priv)
+ return -ENODEV;
+
+ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size,
+ sizeof(chardev_priv->req_buf_size));
}
static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg;
- struct wmi_smbios_priv *priv = filp->private_data;
- long ret;
if (cmd != DELL_WMI_SMBIOS_CMD)
return -ENOIOCTLCMD;
- mutex_lock(&call_mutex);
- ret = dell_smbios_wmi_do_ioctl(priv, input);
- mutex_unlock(&call_mutex);
+ guard(rwsem_read)(&chardev_lock);
- return ret;
+ if (!chardev_priv)
+ return -ENODEV;
+
+ guard(mutex)(&chardev_priv->call_lock);
+
+ return dell_smbios_wmi_do_ioctl(chardev_priv, input);
}
static const struct file_operations dell_smbios_wmi_fops = {
.owner = THIS_MODULE,
- .open = dell_smbios_wmi_open,
.read = dell_smbios_wmi_read,
.unlocked_ioctl = dell_smbios_wmi_ioctl,
.compat_ioctl = compat_ptr_ioctl,
@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data)
misc_deregister(char_dev);
}
+static void dell_smbios_wmi_clear_chardev(void *data)
+{
+ guard(rwsem_write)(&chardev_lock);
+
+ chardev_priv = NULL;
+}
+
static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv)
{
int ret;
+ scoped_guard(rwsem_write, &chardev_lock) {
+ /* We can only have a single chardev at a time */
+ if (chardev_priv)
+ return -EBUSY;
+
+ chardev_priv = priv;
+ }
+
+ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL);
+ if (ret < 0)
+ return ret;
+
priv->char_dev.minor = MISC_DYNAMIC_MINOR;
priv->char_dev.name = "wmi/dell-smbios";
priv->char_dev.fops = &dell_smbios_wmi_fops;
@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
if (!priv->buf)
return -ENOMEM;
+ ret = devm_mutex_init(&wdev->dev, &priv->call_lock);
+ if (ret)
+ return ret;
+
ret = dell_smbios_wmi_register_chardev(priv);
if (ret)
return ret;
- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
- if (ret)
- return ret;
-
- mutex_lock(&list_mutex);
- list_add_tail(&priv->list, &wmi_list);
- mutex_unlock(&list_mutex);
-
- return 0;
+ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
}
static void dell_smbios_wmi_remove(struct wmi_device *wdev)
{
- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
-
- mutex_lock(&call_mutex);
- mutex_lock(&list_mutex);
- list_del(&priv->list);
- mutex_unlock(&list_mutex);
dell_smbios_unregister_device(&wdev->dev);
- mutex_unlock(&call_mutex);
}
static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = {
.probe = dell_smbios_wmi_probe,
.remove = dell_smbios_wmi_remove,
.id_table = dell_smbios_wmi_id_table,
+ .no_singleton = true,
};
int init_dell_smbios_wmi(void)
diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h
--- a/drivers/platform/x86/dell/dell-smbios.h
+++ b/drivers/platform/x86/dell/dell-smbios.h
@@ -47,6 +47,8 @@
struct notifier_block;
+typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer);
+
struct calling_interface_token {
u16 tokenID;
u16 location;
@@ -64,7 +66,7 @@ struct calling_interface_structure {
struct calling_interface_token tokens[];
} __packed;
-int dell_smbios_register_device(struct device *d, int priority, void *call_fn);
+int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn);
void dell_smbios_unregister_device(struct device *d);
int dell_smbios_error(int value);
diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c
--- 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/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
--- 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
--- 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
--- 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);
+ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev));
+ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev));
- snprintf(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-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -14,6 +14,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/dmi.h>
@@ -23,6 +25,7 @@
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
+#include <linux/kstrtox.h>
#include <linux/limits.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -58,6 +61,12 @@ enum hp_ec_offsets {
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
#define HP_POWER_LIMIT_NO_CHANGE 0xFF
+#define HPWMI_MUX_MODE_UMA BIT(0)
+#define HPWMI_MUX_MODE_HYBRID BIT(1)
+#define HPWMI_MUX_MODE_DISCRETE BIT(2)
+#define HPWMI_MUX_MODE_OPTIMUS BIT(3)
+#define HPWMI_MUX_MODE_MASK GENMASK(6, 0)
+#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE)
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
@@ -133,24 +142,41 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = {
.ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET,
};
+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;
@@ -181,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = {
"886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6",
"88F7", "88FD", "88FE", "88FF",
"8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB",
- "8A15", "8A42",
+ "8A15", "8A42", "8A43",
"8BAD",
"8C58",
"8E41",
@@ -217,6 +243,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") },
.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_board_params,
@@ -225,6 +255,14 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") },
.driver_data = (void *)&omen_v1_legacy_board_params,
},
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") },
+ .driver_data = (void *)&omen_v1_board_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") },
+ .driver_data = (void *)&omen_v1_board_params,
+ },
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") },
.driver_data = (void *)&omen_v1_board_params,
@@ -233,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") },
.driver_data = (void *)&victus_s_board_params,
},
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
.driver_data = (void *)&victus_s_board_params,
@@ -289,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") },
.driver_data = (void *)&omen_v1_no_ec_board_params,
},
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") },
+ .driver_data = (void *)&omen_v1_no_ec_board_params,
+ },
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") },
.driver_data = (void *)&omen_v1_no_ec_board_params,
@@ -360,6 +406,7 @@ enum hp_wmi_commandtype {
HPWMI_POSTCODEERROR_QUERY = 0x2a,
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
+ HPWMI_GRAPHICS_MUX_QUERY = 0x52,
};
struct victus_power_limits {
@@ -528,9 +575,9 @@ struct hp_wmi_hwmon_priv {
struct mutex lock; /* protects mode, pwm */
u8 min_rpm;
u8 max_rpm;
- int gpu_delta;
u8 mode;
- u8 pwm;
+ u8 cpu_pwm;
+ u8 gpu_pwm;
struct delayed_work keep_alive_dwork;
};
@@ -857,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled)
return enabled;
}
-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv)
{
u8 fan_speed[2];
- int gpu_speed, ret;
+ int ret;
- fan_speed[CPU_FAN] = speed;
- fan_speed[GPU_FAN] = speed;
+ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC)
+ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
+ else
+ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv);
- /*
- * GPU fan speed is always a little higher than CPU fan speed, we fetch
- * this delta value from the fan table during hwmon init.
- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
- * automatic mode.
- */
- if (speed != HP_FAN_SPEED_AUTOMATIC) {
- gpu_speed = speed + priv->gpu_delta;
- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
- }
+ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC)
+ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
+ else
+ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv);
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
@@ -891,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
{
- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
+ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC;
+ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC;
+ return hp_wmi_fan_speed_set(priv);
}
static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
@@ -1152,12 +1197,134 @@ static int camera_shutter_input_setup(void)
return err;
}
+static const u8 mux_bitmask_map[] = {
+ [0] = HPWMI_MUX_MODE_HYBRID,
+ [1] = HPWMI_MUX_MODE_DISCRETE,
+ [2] = HPWMI_MUX_MODE_OPTIMUS,
+ [3] = HPWMI_MUX_MODE_UMA,
+};
+
+static int hp_wmi_get_mux_supported_modes(u8 *supported)
+{
+ u8 legacy_buffer[4] = {};
+ u8 buffer[128] = {};
+ u32 req_packet = 0;
+ int ret;
+
+ if (!supported)
+ return -EINVAL;
+
+ /* Try modern BIOS design data query (128-byte buffer) */
+ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
+ buffer, zero_if_sup(req_packet), sizeof(buffer));
+ if (ret == 0) {
+ *supported = buffer[7];
+ return 0;
+ }
+
+ /*
+ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub.
+ * If the modern query is not supported, check if the MUX query endpoint
+ * responds to a read request. If it succeeds, the hardware has MUX
+ * capability but lacks the mode map, defaulting to Hybrid + Discrete.
+ */
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ legacy_buffer, sizeof(legacy_buffer), 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ *supported = HPWMI_MUX_LEGACY_MASK;
+ return 0;
+}
+
+static int hp_wmi_get_mux_mode(u8 *mode)
+{
+ u8 buffer[4] = {};
+ int ret;
+
+ if (!mode)
+ return -EINVAL;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ buffer, sizeof(buffer), sizeof(buffer));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ /* Mask the highest bit, which might be used as a BIOS status flag */
+ *mode = buffer[0] & HPWMI_MUX_MODE_MASK;
+ return 0;
+}
+
+static int hp_wmi_set_mux_mode(u8 mode)
+{
+ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 };
+ int ret;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE,
+ buffer, sizeof(buffer), sizeof(buffer));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static ssize_t gpu_mux_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 mode;
+ int ret;
+
+ ret = hp_wmi_get_mux_mode(&mode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", mode);
+}
+
+static ssize_t gpu_mux_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ u32 requested;
+ u8 supported;
+ int ret;
+
+ ret = kstrtou32(buf, 0, &requested);
+ if (ret)
+ return ret;
+ if (requested >= ARRAY_SIZE(mux_bitmask_map))
+ return -EINVAL;
+
+ ret = hp_wmi_get_mux_supported_modes(&supported);
+ if (ret)
+ return ret;
+
+ /* Verify if the requested mode is allowed by the hardware mask */
+ if (!(supported & mux_bitmask_map[requested]))
+ return -EOPNOTSUPP;
+
+ ret = hp_wmi_set_mux_mode(requested);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
static DEVICE_ATTR_RO(display);
static DEVICE_ATTR_RO(hddtemp);
static DEVICE_ATTR_RW(als);
static DEVICE_ATTR_RO(dock);
static DEVICE_ATTR_RO(tablet);
static DEVICE_ATTR_RW(postcode);
+static DEVICE_ATTR_RW(gpu_mux_mode);
static struct attribute *hp_wmi_attrs[] = {
&dev_attr_display.attr,
@@ -1166,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = {
&dev_attr_dock.attr,
&dev_attr_tablet.attr,
&dev_attr_postcode.attr,
+ &dev_attr_gpu_mux_mode.attr,
NULL,
};
ATTRIBUTE_GROUPS(hp_wmi);
@@ -1817,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void)
return is_victus_s_board;
}
+static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void)
+{
+ if (!active_board_params)
+ return NULL;
+
+ return active_board_params->fan_profile;
+}
+
+static bool hp_wmi_fan_control_supported(void)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ return params && params->get_fan_speed;
+}
+
+static bool hp_wmi_fan_table_supported(void)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ return params && params->fan_table;
+}
+
+static int hp_wmi_get_active_fan_speed(int fan)
+{
+ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
+
+ if (!params || !params->get_fan_speed)
+ return -EOPNOTSUPP;
+
+ return params->get_fan_speed(fan);
+}
+
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
bool *ppab_enable,
u8 *dstate,
@@ -2436,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
switch (priv->mode) {
case PWM_MODE_MAX:
- if (is_victus_s_thermal_profile()) {
+ if (hp_wmi_fan_control_supported()) {
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
return ret;
@@ -2448,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_MANUAL:
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
+ ret = hp_wmi_fan_speed_set(priv);
if (ret < 0)
return ret;
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_AUTO:
- if (is_victus_s_thermal_profile()) {
+ if (hp_wmi_fan_control_supported()) {
ret = hp_wmi_get_fan_count_userdefine_trigger();
if (ret < 0)
return ret;
@@ -2481,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
{
switch (type) {
case hwmon_pwm:
- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
+ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported())
return 0;
return 0644;
case hwmon_fan:
- if (is_victus_s_thermal_profile()) {
- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0)
+ if (hp_wmi_fan_control_supported()) {
+ if (hp_wmi_get_active_fan_speed(channel) >= 0)
return 0444;
} else {
if (hp_wmi_get_fan_speed(channel) >= 0)
@@ -2510,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
- if (is_victus_s_thermal_profile())
- ret = hp_wmi_get_fan_speed_victus_s(channel);
+ if (hp_wmi_fan_control_supported())
+ ret = hp_wmi_get_active_fan_speed(channel);
else
ret = hp_wmi_get_fan_speed(channel);
if (ret < 0)
@@ -2520,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
return 0;
case hwmon_pwm:
if (attr == hwmon_pwm_input) {
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
- rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ rpm = hp_wmi_get_active_fan_speed(channel);
if (rpm < 0)
return rpm;
*val = rpm_to_pwm(rpm / 100, priv);
@@ -2550,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct hp_wmi_hwmon_priv *priv;
- int rpm;
+ int cpu_rpm, gpu_rpm;
priv = dev_get_drvdata(dev);
guard(mutex)(&priv->lock);
switch (type) {
case hwmon_pwm:
if (attr == hwmon_pwm_input) {
- if (!is_victus_s_thermal_profile())
+ int rpm;
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
/* PWM input is invalid when not in manual mode */
if (priv->mode != PWM_MODE_MANUAL)
@@ -2566,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
/* ensure PWM input is within valid fan speeds */
rpm = pwm_to_rpm(val, priv);
rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
- priv->pwm = rpm_to_pwm(rpm, priv);
+ if (channel == CPU_FAN)
+ priv->cpu_pwm = rpm_to_pwm(rpm, priv);
+ else if (channel == GPU_FAN)
+ priv->gpu_pwm = rpm_to_pwm(rpm, priv);
return hp_wmi_apply_fan_settings(priv);
}
switch (val) {
@@ -2574,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
priv->mode = PWM_MODE_MAX;
return hp_wmi_apply_fan_settings(priv);
case PWM_MODE_MANUAL:
- if (!is_victus_s_thermal_profile())
+ if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
/*
* When switching to manual mode, set fan speed to
* current RPM values to ensure a smooth transition.
*/
- rpm = hp_wmi_get_fan_speed_victus_s(channel);
- if (rpm < 0)
- return rpm;
- priv->pwm = rpm_to_pwm(rpm / 100, priv);
+ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN);
+ if (cpu_rpm < 0)
+ return cpu_rpm;
+ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN);
+ if (gpu_rpm < 0)
+ return gpu_rpm;
+ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv);
+ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv);
priv->mode = PWM_MODE_MANUAL;
return hp_wmi_apply_fan_settings(priv);
case PWM_MODE_AUTO:
@@ -2599,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT),
NULL
};
@@ -2640,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
struct victus_s_fan_table *fan_table;
u8 min_rpm, max_rpm;
u8 cpu_rpm, gpu_rpm, noise_db;
- int gpu_delta, i, num_entries, ret;
+ int i, num_entries, ret;
size_t header_size, entry_size;
/* Default behaviour on hwmon init is automatic mode */
priv->mode = PWM_MODE_AUTO;
- /* Bypass all non-Victus S devices */
- if (!is_victus_s_thermal_profile())
+ /* Bypass devices without fan control support. */
+ if (!hp_wmi_fan_table_supported())
return 0;
ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
@@ -2686,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
if (min_rpm == U8_MAX || max_rpm == 0)
return -EINVAL;
- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
priv->min_rpm = min_rpm;
priv->max_rpm = max_rpm;
- priv->gpu_delta = gpu_delta;
return 0;
}
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -6,6 +6,7 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev)
/* Fn lock */
+/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */
+#define FN_LOCK_ACPI_OFF 1
+#define FN_LOCK_ACPI_ON 2
+#define FN_LOCK_ACPI_STAT_OK 0
+
+/*
+ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct
+ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control
+ * Fn-lock via EC registers 0x6B (read) and 0x6C (write).
+ *
+ * GFRS response buffer layout:
+ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success)
+ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on)
+ *
+ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)):
+ * Value is read from byte[2] of the integer argument, so it must be
+ * passed as (value << 16):
+ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C)
+ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C)
+ */
+
+static int huawei_acpi_fn_lock_get(int *on)
+{
+ union acpi_object acpi_arg;
+ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ acpi_arg.type = ACPI_TYPE_INTEGER;
+ acpi_arg.integer.value = 0;
+
+ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ union acpi_object *obj __free(kfree) = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2)
+ return -ENODATA;
+
+ /* byte[0] = STAT, byte[1] = fn-lock state */
+ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
+ return -EIO;
+
+ switch (obj->buffer.pointer[1]) {
+ case FN_LOCK_ACPI_OFF:
+ if (on)
+ *on = 0;
+ break;
+ case FN_LOCK_ACPI_ON:
+ if (on)
+ *on = 1;
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int huawei_acpi_fn_lock_set(int on)
+{
+ union acpi_object acpi_arg;
+ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ /*
+ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02).
+ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off)
+ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on)
+ */
+ acpi_arg.type = ACPI_TYPE_INTEGER;
+ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16;
+
+ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ union acpi_object *obj __free(kfree) = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1)
+ return -ENODATA;
+
+ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
+ return -EIO;
+
+ return 0;
+}
+
static int huawei_wmi_fn_lock_get(int *on)
{
u8 ret[0x100] = { 0 };
int err, i;
+ /* Newer models: use direct ACPI \GFRS method */
+ if (acpi_has_method(NULL, "\\GFRS"))
+ return huawei_acpi_fn_lock_get(on);
+
+ /* WMI fallback */
err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);
if (err)
return err;
@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on)
{
union hwmi_arg arg;
+ /* Newer models: use direct ACPI \SFRS method */
+ if (acpi_has_method(NULL, "\\SFRS"))
+ return huawei_acpi_fn_lock_set(on);
+
+ /* WMI fallback */
arg.cmd = FN_LOCK_SET;
arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.
diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c
--- a/drivers/platform/x86/intel/int1092/intel_sar.c
+++ b/drivers/platform/x86/intel/int1092/intel_sar.c
@@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob
item->package.count <= data->total_dev_mode)
return AE_ERROR;
- data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev,
- data->total_dev_mode,
- sizeof(*data->device_mode_info),
- GFP_KERNEL);
+ data->device_mode_info = devm_kcalloc(&context->sar_device->dev,
+ data->total_dev_mode,
+ sizeof(*data->device_mode_info),
+ GFP_KERNEL);
if (!data->device_mode_info)
return AE_ERROR;
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
{
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
- int ret, lpm_size;
+ int ret = 0, lpm_size;
u8 mode;
lpm_indices = pmc->map->lpm_reg_index;
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init)
static void __exit ideapad_laptop_exit(void)
{
- ideapad_wmi_driver_unregister();
platform_driver_unregister(&ideapad_acpi_driver);
+ ideapad_wmi_driver_unregister();
}
module_exit(ideapad_laptop_exit)
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -38,6 +38,7 @@
#include <linux/backlight.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -67,6 +68,7 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/string_choices.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/types.h>
@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t {
TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */
TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */
TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */
+ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */
TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */
TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */
@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct {
struct ibm_struct {
char *name;
+ acpi_device_class device_class;
int (*read) (struct seq_file *);
int (*write) (char *);
@@ -374,6 +378,8 @@ static struct {
u32 has_adaptive_kbd:1;
u32 kbd_lang:1;
u32 trackpoint_doubletap_enable:1;
+ u32 usbc_security_supported:1;
+ bool usbc_security_enabled;
struct quirk_entry *quirks;
} tp_features;
@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
}
ibm->acpi->device->driver_data = ibm;
- scnprintf(acpi_device_class(ibm->acpi->device),
- sizeof(acpi_device_class(ibm->acpi->device)),
- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name);
+ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s",
+ TPACPI_ACPI_EVENT_PREFIX, ibm->name);
status = acpi_install_notify_handler(*ibm->acpi->handle,
ibm->acpi->type, dispatch_acpi_notify, ibm);
@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
pr_err("unknown HKEY notification event %d\n", event);
/* forward it to userspace, maybe it knows how to handle it */
acpi_bus_generate_netlink_event(
- ibm->acpi->device->pnp.device_class,
+ ibm->device_class,
dev_name(&ibm->acpi->device->dev),
event, 0);
return;
@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
/* netlink events */
if (send_acpi_ev) {
acpi_bus_generate_netlink_event(
- ibm->acpi->device->pnp.device_class,
+ ibm->device_class,
dev_name(&ibm->acpi->device->dev),
event, hkey);
}
@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */
TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */
TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */
+ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */
TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */
@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = {
.name = "hwdd",
};
+/*************************************************************************
+ * USB-C Security subdriver
+ *
+ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored.
+ * It always returns:
+ * bit 16 - USB-C security capability present on this SKU or not
+ * bit 0 - USB-C Security state (enable or disable)
+ *
+ * Hotkey
+ * ------
+ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event.
+ * The driver reads back the new state and notifies the sysfs attribute.
+ */
+
+/* USCS() return word bit layout */
+#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */
+#define USCS_STATUS_BIT BIT(0) /* current security state */
+
+/* Protects USCS() ACPI method calls in usbc_security_query() */
+static DEFINE_MUTEX(usbc_security_mutex);
+
+/**
+ * usbc_security_query - read current USB-C security state via USCS()
+ * @enabled: out - true when security is ON (data connections blocked)
+ *
+ * Returns:
+ * 0 success, @enabled contains the current state
+ * -EIO ACPI evaluation failed
+ * -ENODEV capability bit absent; feature not present on this SKU*
+ */
+static int usbc_security_query(bool *enabled)
+{
+ int status;
+
+ guard(mutex)(&usbc_security_mutex);
+ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0))
+ return -EIO;
+
+ if (!(status & USCS_CAP_BIT)) {
+ pr_debug("USCS cap bit absent (raw=0x%x)\n", status);
+ return -ENODEV;
+ }
+
+ *enabled = status & USCS_STATUS_BIT;
+ return 0;
+}
+
+/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */
+static ssize_t usb_c_security_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ str_enabled_disabled(tp_features.usbc_security_enabled));
+}
+
+static DEVICE_ATTR_RO(usb_c_security);
+
+static struct attribute *usbc_security_attributes[] = {
+ &dev_attr_usb_c_security.attr,
+ NULL,
+};
+
+static umode_t usbc_security_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return tp_features.usbc_security_supported ? attr->mode : 0;
+}
+
+static const struct attribute_group usbc_security_attr_group = {
+ .is_visible = usbc_security_attr_is_visible,
+ .attrs = usbc_security_attributes,
+};
+
+static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm)
+{
+ int err;
+
+ if (!acpi_has_method(hkey_handle, "USCS"))
+ return -ENODEV;
+
+ err = usbc_security_query(&tp_features.usbc_security_enabled);
+ if (err == -ENODEV)
+ return 0;
+ if (err)
+ return err;
+
+ tp_features.usbc_security_supported = true;
+ return 0;
+}
+
+/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */
+static bool tpacpi_usbc_security_hotkey(void)
+{
+ if (!tp_features.usbc_security_supported)
+ return false;
+
+ if (usbc_security_query(&tp_features.usbc_security_enabled))
+ return false;
+
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security");
+ return true;
+}
+
+static struct ibm_struct usbc_security_driver_data = {
+ .name = "usbc_security",
+};
+
/* --------------------------------------------------------------------- */
static struct attribute *tpacpi_driver_attributes[] = {
@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&dprc_attr_group,
&auxmac_attr_group,
&hwdd_attr_group,
+ &usbc_security_attr_group,
NULL,
};
@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
case TP_HKEY_EV_PROFILE_TOGGLE2:
platform_profile_cycle();
return true;
+ case TP_HKEY_EV_USB_C_SECURITY:
+ return tpacpi_usbc_security_hotkey();
}
return false;
@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = tpacpi_hwdd_init,
.data = &hwdd_driver_data,
},
+ {
+ .init = tpacpi_usbc_security_init,
+ .data = &usbc_security_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device (_UID "GMZN").
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
+ *
+ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
+ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
+ * The notify callback receives BKBD as an integer; no separate query needed.
+ *
+ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
+ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
+ * WmiDataId(2) uint32 Status = BKBD value
+ * Used on probe and resume to read initial state.
+ *
+ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI
+ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev
+ * and registers a notifier_block to receive those events, eliminating the
+ * need for shared global state or a mutex.
+ *
+ * SW_TABLET_MODE=1 is reported when the keyboard is detached;
+ * SW_TABLET_MODE=0 when docked in either position (keyboard present).
+ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dev_printk.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+/* BKBD encoding */
+#define BKBD_DETACHED 0
+
+/* LENOVO_FEATURE_STATUS_DATA feature selector */
+#define YB9_FEATURE_STATUS_ID 0x00060000u
+
+/*
+ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}.
+ * IDs is always 0x00060000; Status holds the BKBD value (0–3).
+ */
+struct lenovo_feature_status {
+ __le32 id;
+ __le32 status;
+} __packed;
+
+/* ------------------------------------------------------------------
+ * Notifier chain — event driver fires it, block driver listens
+ * ------------------------------------------------------------------ */
+
+static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head);
+
+static void devm_yb9_kbdock_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb);
+}
+
+static int devm_yb9_kbdock_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb);
+}
+
+/* ------------------------------------------------------------------
+ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
+ * (owns input_dev, sysfs, PM resume)
+ * ------------------------------------------------------------------ */
+
+struct yb9_kbdock_data {
+ struct wmi_device *wdev;
+ struct input_dev *input_dev;
+ struct notifier_block nb;
+ spinlock_t lock; /* protects input_report_switch + input_sync */
+};
+
+static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd)
+{
+ struct wmi_buffer out;
+ int ret;
+
+ ret = wmidev_query_block(d->wdev, 0, &out,
+ sizeof(struct lenovo_feature_status));
+ if (ret)
+ return ret;
+
+ struct lenovo_feature_status *fs __free(kfree) = out.data;
+
+ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID)
+ return -EIO;
+
+ *bkbd = le32_to_cpu(fs->status);
+ return 0;
+}
+
+static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd)
+{
+ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ spin_lock(&d->lock);
+ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet);
+ input_sync(d->input_dev);
+ spin_unlock(&d->lock);
+ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet);
+}
+
+static int yb9_kbdock_sync(struct yb9_kbdock_data *d)
+{
+ u32 bkbd;
+ int ret;
+
+ ret = yb9_kbdock_query(d, &bkbd);
+ if (ret)
+ return ret;
+
+ yb9_kbdock_report(d, bkbd);
+ return 0;
+}
+
+static int yb9_kbdock_nb_call(struct notifier_block *nb,
+ unsigned long bkbd, void *unused)
+{
+ struct yb9_kbdock_data *d =
+ container_of(nb, struct yb9_kbdock_data, nb);
+
+ yb9_kbdock_report(d, bkbd);
+ return NOTIFY_DONE;
+}
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct yb9_kbdock_data *d = dev_get_drvdata(dev);
+ u32 bkbd;
+ int ret;
+
+ ret = yb9_kbdock_query(d, &bkbd);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u\n", bkbd);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static const struct attribute * const yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+static int yb9_kbdock_resume(struct device *dev)
+{
+ struct yb9_kbdock_data *d = dev_get_drvdata(dev);
+
+ return yb9_kbdock_sync(d);
+}
+static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
+
+static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct yb9_kbdock_data *d;
+ struct input_dev *input_dev;
+ int ret;
+
+ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ d->wdev = wdev;
+ spin_lock_init(&d->lock);
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ ret = input_register_device(input_dev);
+ if (ret)
+ return ret;
+
+ d->input_dev = input_dev;
+ d->nb.notifier_call = yb9_kbdock_nb_call;
+
+ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&wdev->dev, d);
+ return yb9_kbdock_sync(d);
+}
+
+static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
+ { .guid_string = YB9_KBDOCK_QUERY_GUID },
+ { }
+};
+
+static struct wmi_driver yb9_kbdock_block_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
+ },
+ .id_table = yb9_kbdock_block_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_block_probe,
+};
+
+/* ------------------------------------------------------------------
+ * Event WMI driver — LENOVO_BTKBD_EVENT
+ * (fires the notifier chain on each WMI event)
+ * ------------------------------------------------------------------ */
+
+static void yb9_kbdock_notify_new(struct wmi_device *wdev,
+ const struct wmi_buffer *data)
+{
+ /*
+ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer
+ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
+ * Short-buffer guard is handled by .min_event_size below.
+ */
+ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data);
+
+ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL);
+}
+
+static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
+
+static struct wmi_driver yb9_kbdock_event_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock-event",
+ },
+ .id_table = yb9_kbdock_event_id_table,
+ .no_singleton = true,
+ .notify_new = yb9_kbdock_notify_new,
+ .min_event_size = sizeof(__le32),
+};
+
+/* ------------------------------------------------------------------
+ * Module init / exit
+ * ------------------------------------------------------------------ */
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+static int __init yb9_kbdock_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ ret = wmi_driver_register(&yb9_kbdock_event_driver);
+ if (ret)
+ return ret;
+
+ ret = wmi_driver_register(&yb9_kbdock_block_driver);
+ if (ret) {
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(yb9_kbdock_init);
+
+static void __exit yb9_kbdock_exit(void)
+{
+ wmi_driver_unregister(&yb9_kbdock_block_driver);
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+}
+module_exit(yb9_kbdock_exit);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c
--- a/drivers/platform/x86/lenovo/ymc.c
+++ b/drivers/platform/x86/lenovo/ymc.c
@@ -28,6 +28,22 @@ static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
+static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
+ {
+ /*
+ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
+ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
+ * avoid userspace seeing two input nodes that both advertise
+ * the SW_TABLET_MODE capability.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
{
.matches = {
@@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
struct input_dev *input_dev;
int err;
+ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
+ return -ENODEV;
+
if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
if (force)
dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
--- 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,26 +88,29 @@ 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;
@@ -105,16 +118,65 @@ 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];
@@ -155,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;
}
+}
- return buffer.pointer;
+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 0;
}
static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2)
@@ -212,76 +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;
- unsigned int brightness;
-
- 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) {
- if (kbd_backlight_available) {
- brightness = get_kbd_backlight_level(kbd_backlight.dev->parent);
- led_classdev_notify_brightness_hw_changed(&kbd_backlight,
- brightness);
- }
- } 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);
@@ -290,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;
}
@@ -301,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;
+ u32 mode;
+ int ret;
- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
+ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode);
+ if (ret < 0)
+ return ret;
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value);
- kfree(r);
-
- 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,
@@ -366,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;
+ u32 status;
+ int ret;
- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
+ ret = lg_wmab_get(dev, WM_READER_MODE, &status);
+ if (ret < 0)
+ return ret;
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- status = !!r->integer.value;
-
- kfree(r);
-
- return sysfs_emit(buffer, "%d\n", status);
+ return sysfs_emit(buffer, "%d\n", !!status);
}
static ssize_t fn_lock_store(struct device *dev,
@@ -408,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;
+ u32 status;
+ int ret;
- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0);
- if (!r)
- return -EIO;
+ ret = lg_wmab_get(dev, WM_FN_LOCK, &status);
+ if (ret < 0)
+ return ret;
- if (r->type != ACPI_TYPE_BUFFER) {
- kfree(r);
- return -EIO;
- }
-
- status = !!r->buffer.pointer[0];
- kfree(r);
-
- 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,
@@ -458,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;
}
@@ -476,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);
@@ -490,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;
@@ -568,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)
@@ -584,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);
-
- if (!r)
+ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value);
+ if (ret < 0)
return LED_OFF;
- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {
- kfree(r);
+ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC)
return LED_OFF;
- }
- switch (r->buffer.pointer[0] & 0x27) {
- case 0x24:
- val = LED_FULL;
- break;
- case 0x22:
- val = LED_HALF;
- break;
+ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF)
+ return LED_OFF;
+
+ 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)
@@ -634,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)
-{
- if (inited & INIT_INPUT_WMI_2)
- wmi_remove_notify_handler(WMI_EVENT_GUID2);
-
- if (inited & INIT_INPUT_WMI_0)
- wmi_remove_notify_handler(WMI_EVENT_GUID0);
-
- if (inited & INIT_SPARSE_KEYMAP)
- input_unregister_device(wmi_input_dev);
-
- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);
-}
-
static struct platform_driver pf_driver = {
.driver = {
.name = PLATFORM_NAME,
}
};
+static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data)
+{
+ 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 (result > U32_MAX)
+ return -EPROTO;
+
+ *data = result;
+
+ return 0;
+}
+
+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)
{
@@ -865,10 +1036,6 @@ static int acpi_probe(struct platform_device *pdev)
if (year >= 2019)
battery_limit_use_wmbb = 1;
- ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
- if (ret)
- goto out_platform_device;
-
/* LEDs are optional */
ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight);
if (ret < 0)
@@ -878,7 +1045,14 @@ static int acpi_probe(struct platform_device *pdev)
devm_led_classdev_register(&pdev->dev, &tpad_led);
- wmi_input_setup();
+ 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;
+
battery_hook_register(&battery_hook);
return 0;
@@ -895,7 +1069,6 @@ static void acpi_remove(struct platform_device *pdev)
sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
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
--- a/drivers/platform/x86/msi-ec.c
+++ b/drivers/platform/x86/msi-ec.c
@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = {
static const char * const ALLOWED_FW_13[] __initconst = {
"1594EMS1.109", // MSI Prestige 16 Studio A13VE
+ "17L1EMS1.107", // MSI Katana GF76 11UEK
NULL
};
@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = {
},
};
+static const char * const ALLOWED_FW_14[] __initconst = {
+ "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824)
+ "182LIMS1.108", // Vector A18 HX A9WHG
+ "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG
+ "182KIMS1.113", // Raider A18 HX A7VIG
+ NULL
+};
+
+static struct msi_ec_conf CONF14 __initdata = {
+ .allowed_fw = ALLOWED_FW_14,
+ .charge_control = {
+ .address = 0xd7,
+ .offset_start = 0x8a,
+ .offset_end = 0x80,
+ .range_min = 0x8a,
+ .range_max = 0xe4,
+ },
+ .webcam = {
+ .address = 0x2e,
+ .block_address = 0x2f,
+ .bit = 1,
+ },
+ .fn_win_swap = {
+ .address = 0xe8,
+ .bit = 4,
+ },
+ .cooler_boost = {
+ .address = 0x98,
+ .bit = 7,
+ },
+ .shift_mode = {
+ .address = 0xd2,
+ .modes = {
+ { SM_ECO_NAME, 0xc2 },
+ { SM_COMFORT_NAME, 0xc1 },
+ { SM_TURBO_NAME, 0xc4 },
+ MSI_EC_MODE_NULL
+ },
+ },
+ .super_battery = {
+ .address = 0xeb,
+ .mask = 0x0f,
+ },
+ .fan_mode = {
+ .address = 0xd4,
+ .modes = {
+ { FM_AUTO_NAME, 0x0d },
+ { FM_SILENT_NAME, 0x1d },
+ { FM_ADVANCED_NAME, 0x8d },
+ MSI_EC_MODE_NULL
+ },
+ },
+ .cpu = {
+ .rt_temp_address = 0x68,
+ .rt_fan_speed_address = 0x71,
+ .rt_fan_speed_base_min = 0x00,
+ .rt_fan_speed_base_max = 0x96,
+ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
+ .bs_fan_speed_base_min = 0x00,
+ .bs_fan_speed_base_max = 0x0f,
+ },
+ .gpu = {
+ .rt_temp_address = 0x80,
+ .rt_fan_speed_address = 0x89,
+ },
+ .leds = {
+ .micmute_led_address = 0x2c,
+ .mute_led_address = 0x2d,
+ .bit = 1,
+ },
+ .kbd_bl = {
+ .bl_mode_address = MSI_EC_ADDR_UNSUPP,
+ .bl_modes = { 0x00, 0x08 },
+ .max_mode = 1,
+ .bl_state_address = MSI_EC_ADDR_UNSUPP,
+ .state_base_value = 0x80,
+ .max_state = 3,
+ },
+};
+
static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF0,
&CONF1,
@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF11,
&CONF12,
&CONF13,
+ &CONF14,
NULL
};
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
--- 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 && obj->type == ACPI_TYPE_INTEGER) {
- int eventcode = obj->integer.value;
+ if (!obj)
+ return;
+
+ 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;
+
+ /* 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) {
+ 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;
}
+ last_pressed = cur;
+ }
- 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;
- }
+ /* Brightness is served via acpi video driver */
+ if (key->type == KE_KEY &&
+ (backlight || (key->code == MSI_KEY_BRIGHTNESSUP ||
+ key->code == MSI_KEY_BRIGHTNESSDOWN)))
+ return;
- 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);
- }
- } else
- pr_info("Unknown event received\n");
+ pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode);
+ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true);
}
static int __init msi_wmi_backlight_setup(void)
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)oxp_x1,
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
{},
};
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
--- 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"
@@ -1026,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
pcc->device = device;
pcc->handle = device->handle;
device->driver_data = pcc;
- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
- strscpy(acpi_device_class(device), ACPI_PCC_CLASS);
result = acpi_pcc_init_input(pcc);
if (result) {
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
--- a/drivers/platform/x86/redmi-wmi.c
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = {
{KE_KEY, 0x00011801, {KEY_ASSISTANT}},
{KE_KEY, 0x00011901, {KEY_ASSISTANT}},
- /* Keyboard backlight */
- {KE_IGNORE, 0x00000501, {}},
- {KE_IGNORE, 0x00800501, {}},
- {KE_IGNORE, 0x00050501, {}},
- {KE_IGNORE, 0x000a0501, {}},
+ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */
+ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}},
+ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}},
/* Xiaomi G Command Center */
{KE_KEY, 0x00010a01, {KEY_VENDOR}},
- /* OEM preset power mode */
- {KE_IGNORE, 0x00011601, {}},
- {KE_IGNORE, 0x00021601, {}},
- {KE_IGNORE, 0x00031601, {}},
- {KE_IGNORE, 0x00041601, {}},
+ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */
+ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}},
+ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}},
- /* Fn Lock state */
- {KE_IGNORE, 0x00000701, {}},
- {KE_IGNORE, 0x00010701, {}},
+ /* Fn Lock state: 1=on 0=off */
+ {KE_KEY, 0x00000701, {KEY_FN_ESC}},
+ {KE_KEY, 0x00010701, {KEY_FN_ESC}},
/* Fn+`/1/2/3/4 */
{KE_KEY, 0x00011101, {KEY_F13}},
diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c
--- a/drivers/platform/x86/samsung-galaxybook.c
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = {
{ "SAM0428" },
{ "SAM0429" },
{ "SAM0430" },
+ { "SAMB430" },
{}
};
MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
--- 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
--- 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
--- 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
--- 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
--- 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
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -95,6 +95,9 @@
#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
+#define EC_ADDR_SCREEN_STATUS 0x0466
+#define SCREEN_SUSPENDED BIT(6)
+
#define EC_ADDR_SECOND_FAN_RPM_1 0x046C
#define EC_ADDR_SECOND_FAN_RPM_2 0x046D
@@ -109,9 +112,35 @@
#define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7
+#define EC_ADDR_OEM_9 0x0726
+#define AC_AUTO_BOOT_ENABLE BIT(3)
+
#define EC_ADDR_PROJECT_ID 0x0740
+#define PROJECT_ID_NONE 0x00
+#define PROJECT_ID_GI 0x01
+#define PROJECT_ID_GJ 0x02
+#define PROJECT_ID_GK 0x03
+#define PROJECT_ID_GICN 0x04
+#define PROJECT_ID_GJCN 0x05
+#define PROJECT_ID_GK5CN_X 0x06
+#define PROJECT_ID_GK7CN_S 0x07
+#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08
+#define PROJECT_ID_PF 0x09
+#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A
+#define PROJECT_ID_IDP 0x0B
+#define PROJECT_ID_IDY_6Y 0x0C
+#define PROJECT_ID_IDY_7Y 0x0D
+#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E
+#define PROJECT_ID_CML_GAMING 0x0F
+#define PROJECT_ID_GK7NXXR 0x10
+#define PROJECT_ID_GM5MU1Y 0x11
#define PROJECT_ID_PH4TRX1 0x12
+#define PROJECT_ID_PH4TUX1 0x13
+#define PROJECT_ID_PH4TQX1 0x14
#define PROJECT_ID_PH6TRX1 0x15
+#define PROJECT_ID_PH6TQXX 0x16
+#define PROJECT_ID_PHXAXXX 0x17
+#define PROJECT_ID_PHXPXXX 0x18
#define EC_ADDR_AP_OEM 0x0741
#define ENABLE_MANUAL_CTRL BIT(0)
@@ -214,6 +243,7 @@
#define FAN_TABLE_OFFICE_MODE BIT(2)
#define FAN_V3 BIT(3)
#define DEFAULT_MODE BIT(4)
+#define ENABLE_CHINA_MODE BIT(6)
#define EC_ADDR_PL1_SETTING 0x0783
@@ -225,11 +255,12 @@
#define FAN_CURVE_LENGTH 5
#define EC_ADDR_KBD_STATUS 0x078C
-#define KBD_WHITE_ONLY BIT(0) // ~single color
-#define KBD_SINGLE_COLOR_OFF BIT(1)
+/* Unreliable on some models, use the device descriptor instead. */
+#define KBD_WHITE_ONLY BIT(0)
+#define KBD_POWER_OFF BIT(1)
#define KBD_TURBO_LEVEL_MASK GENMASK(3, 2)
#define KBD_APPLY BIT(4)
-#define KBD_BRIGHTNESS GENMASK(7, 5)
+#define KBD_BRIGHTNESS_MASK GENMASK(7, 5)
#define EC_ADDR_FAN_CTRL 0x078E
#define FAN3P5 BIT(1)
@@ -318,7 +349,9 @@
#define FAN_TABLE_LENGTH 16
#define LED_CHANNELS 3
-#define LED_MAX_BRIGHTNESS 200
+
+#define KBD_LED_CHANNELS 3
+#define KBD_LED_MAX_INTENSITY 50
#define UNIWILL_FEATURE_FN_LOCK BIT(0)
#define UNIWILL_FEATURE_SUPER_KEY BIT(1)
@@ -333,6 +366,9 @@
#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9)
#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10)
#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11)
+#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12)
+#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13)
+#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14)
enum usb_c_power_priority_options {
USB_C_POWER_PRIORITY_CHARGING = 0,
@@ -344,6 +380,7 @@ struct uniwill_data {
acpi_handle handle;
struct regmap *regmap;
unsigned int features;
+ u8 project_id;
struct acpi_battery_hook hook;
struct mutex battery_lock; /* Protects the list of currently registered batteries */
union {
@@ -357,11 +394,25 @@ struct uniwill_data {
bool last_fn_lock_state;
bool last_super_key_enable_state;
bool last_touchpad_toggle_enable_state;
+ bool last_usb_powershare_high_state;
struct mutex super_key_lock; /* Protects the toggling of the super key lock state */
struct list_head batteries;
struct mutex led_lock; /* Protects writes to the lightbar registers */
+ u8 lightbar_max_brightness;
struct led_classdev_mc led_mc_cdev;
struct mc_subled led_mc_subled_info[LED_CHANNELS];
+ bool kbd_led_single_color;
+ u8 kbd_led_max_brightness;
+ unsigned int last_kbd_status;
+ union {
+ struct {
+ /* Protects writes to the RGB keyboard backlight registers */
+ struct mutex kbd_rgb_led_lock;
+ struct led_classdev_mc kbd_led_mc_cdev;
+ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS];
+ };
+ struct led_classdev kbd_led_cdev;
+ };
struct mutex input_lock; /* Protects input sequence during notify */
struct input_dev *input_device;
struct notifier_block nb;
@@ -376,6 +427,9 @@ struct uniwill_battery_entry {
struct uniwill_device_descriptor {
unsigned int features;
+ bool kbd_led_single_color;
+ u8 kbd_led_max_brightness;
+ u8 lightbar_max_brightness;
/* Executed during driver probing */
int (*probe)(struct uniwill_data *data);
};
@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = {
{ KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }},
{ KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }},
+ /* Reported when the EC changed the keyboard backlight brightness */
+ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }},
+
/* Reported when the user wants to toggle the microphone mute status */
{ KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }},
@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the brightness of the keyboard */
{ KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }},
- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }},
/* FIXME: find out the exact meaning of those events */
{ KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }},
@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the benchmark mode status */
{ KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }},
+ /* Reported when the screen is enabled/disabled during resume/suspend */
+ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }},
+
/* Reported when the user wants to toggle the webcam */
{ KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = {
static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
+ case EC_ADDR_OEM_9:
case EC_ADDR_AP_OEM:
case EC_ADDR_LIGHTBAR_AC_CTRL:
case EC_ADDR_LIGHTBAR_AC_RED:
@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_AC_BLUE:
case EC_ADDR_BIOS_OEM:
case EC_ADDR_TRIGGER:
+ case EC_ADDR_RGB_RED:
+ case EC_ADDR_RGB_GREEN:
+ case EC_ADDR_RGB_BLUE:
+ case EC_ADDR_BIOS_OEM_2:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_LIGHTBAR_BAT_CTRL:
@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_SECOND_FAN_RPM_1:
case EC_ADDR_SECOND_FAN_RPM_2:
case EC_ADDR_BAT_ALERT:
+ case EC_ADDR_OEM_9:
case EC_ADDR_PROJECT_ID:
case EC_ADDR_AP_OEM:
case EC_ADDR_LIGHTBAR_AC_CTRL:
@@ -583,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_BIOS_OEM:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
+ case EC_ADDR_SUPPORT_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_RGB_RED:
+ case EC_ADDR_RGB_GREEN:
+ case EC_ADDR_RGB_BLUE:
+ case EC_ADDR_BIOS_OEM_2:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_LIGHTBAR_BAT_CTRL:
@@ -616,8 +684,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
case EC_ADDR_BIOS_OEM:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
+ case EC_ADDR_SUPPORT_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_KBD_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_USB_C_POWER_PRIORITY:
@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data)
return 0;
}
+static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ regval = AC_AUTO_BOOT_ENABLE;
+ else
+ regval = 0;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, &regval);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE));
+}
+
+static DEVICE_ATTR_RW(ac_auto_boot);
+
+static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status)
+{
+ unsigned int value;
+
+ if (status)
+ value = TRIGGER_USB_CHARGING;
+ else
+ value = 0;
+
+ /*
+ * Normaly this RMW-sequence could also trigger the super key toggle,
+ * but the EC seems to take care that those bits are always read as 0.
+ */
+ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value);
+}
+
+static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_write_usb_powershare_high(data, enable);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value);
+ if (ret < 0)
+ return ret;
+
+ *status = !!(value & TRIGGER_USB_CHARGING);
+
+ return 0;
+}
+
+static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ bool status;
+ int ret;
+
+ ret = uniwill_read_usb_powershare_high(data, &status);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", status);
+}
+
+static DEVICE_ATTR_RW(usb_powershare_high);
+
static struct attribute *uniwill_attrs[] = {
/* Keyboard-related */
&dev_attr_fn_lock.attr,
@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = {
/* Power-management-related */
&dev_attr_ctgp_offset.attr,
&dev_attr_usb_c_power_priority.attr,
+ &dev_attr_ac_auto_boot.attr,
+ &dev_attr_usb_powershare_high.attr,
NULL
};
@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a
return attr->mode;
}
+ if (attr == &dev_attr_ac_auto_boot.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_usb_powershare_high.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return attr->mode;
+ }
+
return 0;
}
@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br
for (int i = 0; i < LED_CHANNELS; i++) {
/* Prevent the brightness values from overflowing */
- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness);
+ value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness);
ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
if (ret < 0)
return ret;
@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data)
return ret;
data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS;
+ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness;
data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT;
data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set;
if (value & LIGHTBAR_S0_OFF)
data->led_mc_cdev.led_cdev.brightness = 0;
else
- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS;
+ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness;
for (int i = 0; i < LED_CHANNELS; i++) {
data->led_mc_subled_info[i].color_index = color_indices[i];
@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data)
* Make sure that the initial intensity value is not greater than
* the maximum brightness.
*/
- value = min(LED_MAX_BRIGHTNESS, value);
+ value = min(data->lightbar_max_brightness, value);
ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
if (ret < 0)
return ret;
@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data)
&init_data);
}
+static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness)
+{
+ struct led_classdev *led_cdev;
+ int ret;
+
+ if (data->kbd_led_single_color)
+ led_cdev = &data->kbd_led_cdev;
+ else
+ led_cdev = &data->kbd_led_mc_cdev.led_cdev;
+
+ guard(mutex)(&led_cdev->led_access);
+
+ /* Sync the LED brightness with the actual hardware state */
+ ret = led_update_brightness(led_cdev);
+ if (ret < 0)
+ return ret;
+
+ led_classdev_notify_brightness_hw_changed(led_cdev, brightness);
+
+ return 0;
+}
+
+#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF)
+
+static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness)
+{
+ /* KBD_POWER_OFF is always implicitly cleared */
+ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY;
+
+ /* We must ensure that the "apply" bit is always written */
+ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval);
+}
+
+static int uniwill_kbd_led_read_brightness(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness);
+}
+
+static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev);
+
+ return uniwill_kbd_led_write_brightness(data, brightness);
+}
+
+static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev)
+{
+ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev);
+
+ return uniwill_kbd_led_read_brightness(data);
+}
+
+static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = {
+ EC_ADDR_RGB_RED,
+ EC_ADDR_RGB_GREEN,
+ EC_ADDR_RGB_BLUE,
+};
+
+static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev);
+ unsigned int min_intensity = 0;
+ unsigned int regval;
+ int ret;
+
+ guard(mutex)(&data->kbd_rgb_led_lock);
+
+ /*
+ * The EC interprets a RGB value of 0x000000 as a command to restore
+ * the device-specfic default RGB value. Work around this by writing
+ * a RGB value of 0x010101 (faint white) instead.
+ */
+ if (data->kbd_led_mc_subled_info[0].intensity == 0 &&
+ data->kbd_led_mc_subled_info[1].intensity == 0 &&
+ data->kbd_led_mc_subled_info[2].intensity == 0)
+ min_intensity = 1;
+
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity);
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_kbd_led_write_brightness(data, brightness);
+}
+
+static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev);
+
+ return uniwill_kbd_led_read_brightness(data);
+}
+
+static int uniwill_white_kbd_led_init(struct uniwill_data *data)
+{
+ struct led_init_data init_data = {
+ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT,
+ .devicename = DRIVER_NAME,
+ .devname_mandatory = true,
+ };
+
+ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness;
+ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE;
+ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT;
+ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set;
+ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get;
+
+ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data);
+}
+
+static int uniwill_rgb_kbd_led_init(struct uniwill_data *data)
+{
+ unsigned int color_indices[KBD_LED_CHANNELS] = {
+ LED_COLOR_ID_RED,
+ LED_COLOR_ID_GREEN,
+ LED_COLOR_ID_BLUE,
+ };
+ struct led_init_data init_data = {
+ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT,
+ .devicename = DRIVER_NAME,
+ .devname_mandatory = true,
+ };
+ bool intensity_all_zeros = true;
+ bool needs_trigger = false;
+ unsigned int regval;
+ int ret;
+
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ data->kbd_led_mc_subled_info[i].color_index = color_indices[i];
+
+ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], &regval);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Make sure that the initial intensity value is not greater than
+ * the maximum intensity.
+ */
+ if (regval > KBD_LED_MAX_INTENSITY) {
+ regval = KBD_LED_MAX_INTENSITY;
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval);
+ if (ret < 0)
+ return ret;
+
+ needs_trigger = true;
+ }
+
+ if (regval)
+ intensity_all_zeros = false;
+
+ data->kbd_led_mc_subled_info[i].intensity = regval;
+ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY;
+ data->kbd_led_mc_subled_info[i].channel = i;
+ }
+
+ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */
+ if (intensity_all_zeros) {
+ for (int i = 0; i < KBD_LED_CHANNELS; i++) {
+ data->kbd_led_mc_subled_info[i].intensity = 1;
+ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ needs_trigger = true;
+ }
+
+ if (needs_trigger) {
+ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR,
+ RGB_APPLY_COLOR);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock);
+ if (ret < 0)
+ return ret;
+
+ data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness;
+ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
+ data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT;
+ data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set;
+ data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get;
+ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info;
+ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS;
+
+ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev,
+ &init_data);
+}
+
+static int uniwill_kbd_led_init(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &regval);
+ if (ret < 0)
+ return ret;
+
+ if (!(regval & CHINA_MODE)) {
+ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval |= KBD_APPLY;
+ regval &= ~KBD_POWER_OFF;
+ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval);
+ if (ret < 0)
+ return ret;
+
+ if (data->kbd_led_single_color)
+ return uniwill_white_kbd_led_init(data);
+
+ return uniwill_rgb_kbd_led_init(data);
+}
+
static unsigned int uniwill_sanitize_battery_threshold(unsigned int value)
{
/* 0 means "charging threshold not active" */
@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
sysfs_notify(&data->dev->kobj, NULL, "fn_lock");
return NOTIFY_OK;
+ case UNIWILL_OSD_KB_LED_LEVEL0:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 0));
+ case UNIWILL_OSD_KB_LED_LEVEL1:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 1));
+ case UNIWILL_OSD_KB_LED_LEVEL2:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 2));
+ case UNIWILL_OSD_KB_LED_LEVEL3:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 3));
+ case UNIWILL_OSD_KB_LED_LEVEL4:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(uniwill_notify_kbd_led(data, 4));
default:
mutex_lock(&data->input_lock);
sparse_keymap_report_event(data->input_device, action, 1, true);
@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data)
if (ret < 0)
return ret;
+ data->project_id = value;
dev_dbg(data->dev, "Project ID: %u\n", value);
ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL);
@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev)
return ret;
data->features = device_descriptor.features;
+ data->kbd_led_single_color = device_descriptor.kbd_led_single_color;
+ data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness;
+ data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness;
/*
* Some devices might need to perform some device-specific initialization steps
@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ ret = uniwill_kbd_led_init(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_hwmon_init(data);
if (ret < 0)
return ret;
@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data)
return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
}
+static int uniwill_suspend_kbd_led(struct uniwill_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, &regval);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Save the current keyboard backlight settings in order to restore them
+ * during resume. We cannot use the regmap code for that since this register
+ * needs to be declared as volatile because the brightness can be changed
+ * by the EC.
+ */
+ data->last_kbd_status = regval;
+ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, &regval, 0);
+ regval |= KBD_APPLY | KBD_POWER_OFF;
+
+ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval);
+}
+
+static int uniwill_suspend_usb_powershare(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return 0;
+
+ /*
+ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it
+ * ourselves.
+ */
+ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state);
+}
+
static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_suspend_kbd_led(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_suspend_usb_powershare(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_suspend_nvidia_ctgp(data);
if (ret < 0)
return ret;
@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data)
return 0;
}
+static int uniwill_resume_kbd_led(struct uniwill_data *data)
+{
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT))
+ return 0;
+
+ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY);
+ if (ret < 0)
+ return ret;
+
+ if (data->kbd_led_single_color)
+ return 0;
+
+ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR);
+}
+
+static int uniwill_resume_usb_powershare(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE))
+ return 0;
+
+ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state);
+}
+
static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_resume_kbd_led(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_usb_powershare(data);
+ if (ret < 0)
+ return ret;
+
ret = uniwill_resume_nvidia_ctgp(data);
if (ret < 0)
return ret;
@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = {
.shutdown = uniwill_shutdown,
};
+static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+ .kbd_led_single_color = false,
+ .kbd_led_max_brightness = 4,
+};
+
static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = {
.features = UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
+ .lightbar_max_brightness = 36,
};
static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
+ .lightbar_max_brightness = 200,
};
/*
@@ -2198,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini
UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
};
-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = {
- .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,
-};
-
static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
@@ -2218,6 +2763,30 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini
UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
};
+static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+};
+
+static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+};
+
static int phxtxx1_probe(struct uniwill_data *data)
{
unsigned int value;
@@ -2277,7 +2846,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_single_color = true,
+ .kbd_led_max_brightness = 2,
+};
+
static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
+ {
+ .ident = "AiStone X4SP4NAL",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"),
+ },
+ .driver_data = &x4sp4nal_descriptor,
+ },
+ {
+ .ident = "MACHENIKE L16 Pro",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"),
+ },
+ .driver_data = &machenike_l16p_descriptor,
+ },
{
.ident = "XMG FUSION 15 (L19)",
.matches = {
@@ -2310,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
},
.driver_data = &lapqc71a_lapqc71b_descriptor,
},
+ {
+ .ident = "Avell A60 MUV",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
{
.ident = "Intel NUC x15",
.matches = {
@@ -2388,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
},
- .driver_data = &tux_featureset_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
@@ -2396,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
},
- .driver_data = &tux_featureset_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -2404,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
},
- .driver_data = &tux_featureset_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -2412,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
},
- .driver_data = &tux_featureset_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -2420,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
},
- .driver_data = &tux_featureset_3_descriptor,
+ .driver_data = &tux_featureset_4_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -2428,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -2436,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -2444,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -2452,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -2460,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -2620,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
@@ -2628,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -2636,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -2644,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -2652,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -2660,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -2668,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -2676,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
},
- .driver_data = &tux_featureset_3_nvidia_descriptor,
+ .driver_data = &tux_featureset_4_nvidia_descriptor,
},
{
.ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void)
if (force) {
/* Assume that the device supports all features except the charge limit */
device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT;
+ /* Some models only have a (white) single color keyboard backlight */
+ device_descriptor.kbd_led_single_color = false;
+ /* Some models only support 3 brightness levels */
+ device_descriptor.kbd_led_max_brightness = 4;
+ /* Some models only support 36 brightness levels per color component */
+ device_descriptor.lightbar_max_brightness = 200;
pr_warn("Enabling potentially unsupported features\n");
}
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
--- 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
--- 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
--- 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
--- 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
--- 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]
@@ -69,6 +60,7 @@ enum laptop_placement {
* @mpnpuclk_freq: MPNPU [MHz]
* @npu_reads: NPU read bandwidth [MB/sec]
* @npu_writes: NPU write bandwidth [MB/sec]
+ * @npu_temp: NPU temperature [C]
*/
struct amd_pmf_npu_metrics {
u16 npuclk_freq;
@@ -77,6 +69,7 @@ struct amd_pmf_npu_metrics {
u16 mpnpuclk_freq;
u16 npu_reads;
u16 npu_writes;
+ u16 npu_temp;
};
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
--- 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
--- /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 */