8291 lines
255 KiB
Diff
8291 lines
255 KiB
Diff
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, ®val);
|
||
+ 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, ®val);
|
||
+ 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], ®val);
|
||
+ 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, ®val);
|
||
+ 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, ®val);
|
||
+ 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, ®val);
|
||
+ 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, ®val, 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 */
|