summaryrefslogtreecommitdiff
path: root/drivers/platform/x86
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86')
-rw-r--r--drivers/platform/x86/acer-wmi.c9
-rw-r--r--drivers/platform/x86/amd/Kconfig2
-rw-r--r--drivers/platform/x86/amd/Makefile1
-rw-r--r--drivers/platform/x86/amd/pmf/Kconfig16
-rw-r--r--drivers/platform/x86/amd/pmf/Makefile9
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c288
-rw-r--r--drivers/platform/x86/amd/pmf/auto-mode.c305
-rw-r--r--drivers/platform/x86/amd/pmf/core.c388
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h327
-rw-r--r--drivers/platform/x86/amd/pmf/sps.c146
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c38
-rw-r--r--drivers/platform/x86/asus-wmi.c622
-rw-r--r--drivers/platform/x86/asus-wmi.h10
-rw-r--r--drivers/platform/x86/dell/dell-wmi-base.c3
-rw-r--r--drivers/platform/x86/dell/dell-wmi-privacy.c12
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/sysman.c2
-rw-r--r--drivers/platform/x86/intel/chtwc_int33fe.c8
-rw-r--r--drivers/platform/x86/msi-laptop.c96
-rw-r--r--drivers/platform/x86/p2sb.c18
-rw-r--r--drivers/platform/x86/pmc_atom.c44
-rw-r--r--drivers/platform/x86/simatic-ipc.c10
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c11
-rw-r--r--drivers/platform/x86/toshiba_acpi.c5
-rw-r--r--drivers/platform/x86/wmi.c49
-rw-r--r--drivers/platform/x86/x86-android-tablets.c14
25 files changed, 2046 insertions, 387 deletions
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index b933a5165edb..9610742fe6aa 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -99,6 +99,7 @@ static const struct key_entry acer_wmi_keymap[] __initconst = {
{KE_KEY, 0x22, {KEY_PROG2} }, /* Arcade */
{KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */
{KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */
+ {KE_KEY, 0x27, {KEY_HELP} },
{KE_KEY, 0x29, {KEY_PROG3} }, /* P_Key for TM8372 */
{KE_IGNORE, 0x41, {KEY_MUTE} },
{KE_IGNORE, 0x42, {KEY_PREVIOUSSONG} },
@@ -112,7 +113,13 @@ static const struct key_entry acer_wmi_keymap[] __initconst = {
{KE_IGNORE, 0x48, {KEY_VOLUMEUP} },
{KE_IGNORE, 0x49, {KEY_VOLUMEDOWN} },
{KE_IGNORE, 0x4a, {KEY_VOLUMEDOWN} },
- {KE_IGNORE, 0x61, {KEY_SWITCHVIDEOMODE} },
+ /*
+ * 0x61 is KEY_SWITCHVIDEOMODE. Usually this is a duplicate input event
+ * with the "Video Bus" input device events. But sometimes it is not
+ * a dup. Map it to KEY_UNKNOWN instead of using KE_IGNORE so that
+ * udev/hwdb can override it on systems where it is not a dup.
+ */
+ {KE_KEY, 0x61, {KEY_UNKNOWN} },
{KE_IGNORE, 0x62, {KEY_BRIGHTNESSUP} },
{KE_IGNORE, 0x63, {KEY_BRIGHTNESSDOWN} },
{KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index c0d0a3c5170c..a825af8126c8 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -3,6 +3,8 @@
# AMD x86 Platform Specific Drivers
#
+source "drivers/platform/x86/amd/pmf/Kconfig"
+
config AMD_PMC
tristate "AMD SoC PMC driver"
depends on ACPI && PCI && RTC_CLASS
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index a03fbb08e808..2c229198e24c 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -8,3 +8,4 @@ amd-pmc-y := pmc.o
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
amd_hsmp-y := hsmp.o
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
+obj-$(CONFIG_AMD_PMF) += pmf/
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
new file mode 100644
index 000000000000..c375498c4071
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD PMF Driver
+#
+
+config AMD_PMF
+ tristate "AMD Platform Management Framework"
+ depends on ACPI && PCI
+ select ACPI_PLATFORM_PROFILE
+ help
+ This driver provides support for the AMD Platform Management Framework.
+ The goal is to enhance end user experience by making AMD PCs smarter,
+ quiter, power efficient by adapting to user behavior and environment.
+
+ To compile this driver as a module, choose M here: the module will
+ be called amd_pmf.
diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
new file mode 100644
index 000000000000..ef2b08c9174d
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/amd/pmf
+# AMD Platform Management Framework
+#
+
+obj-$(CONFIG_AMD_PMF) += amd-pmf.o
+amd-pmf-objs := core.o acpi.o sps.o \
+ auto-mode.o
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
new file mode 100644
index 000000000000..cb46a7252414
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#include <linux/acpi.h>
+#include "pmf.h"
+
+#define APMF_CQL_NOTIFICATION 2
+#define APMF_AMT_NOTIFICATION 3
+
+static union acpi_object *apmf_if_call(struct amd_pmf_dev *pdev, int fn, struct acpi_buffer *param)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_handle ahandle = ACPI_HANDLE(pdev->dev);
+ struct acpi_object_list apmf_if_arg_list;
+ union acpi_object apmf_if_args[2];
+ acpi_status status;
+
+ apmf_if_arg_list.count = 2;
+ apmf_if_arg_list.pointer = &apmf_if_args[0];
+
+ apmf_if_args[0].type = ACPI_TYPE_INTEGER;
+ apmf_if_args[0].integer.value = fn;
+
+ if (param) {
+ apmf_if_args[1].type = ACPI_TYPE_BUFFER;
+ apmf_if_args[1].buffer.length = param->length;
+ apmf_if_args[1].buffer.pointer = param->pointer;
+ } else {
+ apmf_if_args[1].type = ACPI_TYPE_INTEGER;
+ apmf_if_args[1].integer.value = 0;
+ }
+
+ status = acpi_evaluate_object(ahandle, "APMF", &apmf_if_arg_list, &buffer);
+ if (ACPI_FAILURE(status)) {
+ dev_err(pdev->dev, "APMF method:%d call failed\n", fn);
+ kfree(buffer.pointer);
+ return NULL;
+ }
+
+ return buffer.pointer;
+}
+
+static int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
+{
+ union acpi_object *info;
+ size_t size;
+ int err = 0;
+
+ info = apmf_if_call(pdev, fn, NULL);
+ if (!info)
+ return -EIO;
+
+ if (info->type != ACPI_TYPE_BUFFER) {
+ dev_err(pdev->dev, "object is not a buffer\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (info->buffer.length < 2) {
+ dev_err(pdev->dev, "buffer too small\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ size = *(u16 *)info->buffer.pointer;
+ if (info->buffer.length < size) {
+ dev_err(pdev->dev, "buffer smaller then headersize %u < %zu\n",
+ info->buffer.length, size);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (size < out_sz) {
+ dev_err(pdev->dev, "buffer too small %zu\n", size);
+ err = -EINVAL;
+ goto out;
+ }
+
+ memcpy(dest, info->buffer.pointer, out_sz);
+
+out:
+ kfree(info);
+ return err;
+}
+
+int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
+{
+ /* If bit-n is set, that indicates function n+1 is supported */
+ return !!(pdev->supported_func & BIT(index - 1));
+}
+
+int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
+ struct apmf_static_slider_granular_output *data)
+{
+ if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ return -EINVAL;
+
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
+ data, sizeof(*data));
+}
+
+static void apmf_sbios_heartbeat_notify(struct work_struct *work)
+{
+ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, heart_beat.work);
+ union acpi_object *info;
+
+ dev_dbg(dev->dev, "Sending heartbeat to SBIOS\n");
+ info = apmf_if_call(dev, APMF_FUNC_SBIOS_HEARTBEAT, NULL);
+ if (!info)
+ goto out;
+
+ schedule_delayed_work(&dev->heart_beat, msecs_to_jiffies(dev->hb_interval * 1000));
+
+out:
+ kfree(info);
+}
+
+int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx)
+{
+ union acpi_object *info;
+ struct apmf_fan_idx args;
+ struct acpi_buffer params;
+ int err = 0;
+
+ args.size = sizeof(args);
+ args.fan_ctl_mode = manual;
+ args.fan_ctl_idx = idx;
+
+ params.length = sizeof(args);
+ params.pointer = (void *)&args;
+
+ info = apmf_if_call(pdev, APMF_FUNC_SET_FAN_IDX, &params);
+ if (!info) {
+ err = -EIO;
+ goto out;
+ }
+
+out:
+ kfree(info);
+ return err;
+}
+
+int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
+}
+
+int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
+ req, sizeof(*req));
+}
+
+static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
+{
+ struct amd_pmf_dev *pmf_dev = data;
+ struct apmf_sbios_req req;
+ int ret;
+
+ mutex_lock(&pmf_dev->update_mutex);
+ ret = apmf_get_sbios_requests(pmf_dev, &req);
+ if (ret) {
+ dev_err(pmf_dev->dev, "Failed to get SBIOS requests:%d\n", ret);
+ goto out;
+ }
+
+ if (req.pending_req & BIT(APMF_AMT_NOTIFICATION)) {
+ dev_dbg(pmf_dev->dev, "AMT is supported and notifications %s\n",
+ req.amt_event ? "Enabled" : "Disabled");
+ pmf_dev->amt_enabled = !!req.amt_event;
+
+ if (pmf_dev->amt_enabled)
+ amd_pmf_handle_amt(pmf_dev);
+ else
+ amd_pmf_reset_amt(pmf_dev);
+ }
+
+ if (req.pending_req & BIT(APMF_CQL_NOTIFICATION)) {
+ dev_dbg(pmf_dev->dev, "CQL is supported and notifications %s\n",
+ req.cql_event ? "Enabled" : "Disabled");
+
+ /* update the target mode information */
+ if (pmf_dev->amt_enabled)
+ amd_pmf_update_2_cql(pmf_dev, req.cql_event);
+ }
+out:
+ mutex_unlock(&pmf_dev->update_mutex);
+}
+
+static int apmf_if_verify_interface(struct amd_pmf_dev *pdev)
+{
+ struct apmf_verify_interface output;
+ int err;
+
+ err = apmf_if_call_store_buffer(pdev, APMF_FUNC_VERIFY_INTERFACE, &output, sizeof(output));
+ if (err)
+ return err;
+
+ pdev->supported_func = output.supported_functions;
+ dev_dbg(pdev->dev, "supported functions:0x%x notifications:0x%x\n",
+ output.supported_functions, output.notification_mask);
+
+ return 0;
+}
+
+static int apmf_get_system_params(struct amd_pmf_dev *dev)
+{
+ struct apmf_system_params params;
+ int err;
+
+ if (!is_apmf_func_supported(dev, APMF_FUNC_GET_SYS_PARAMS))
+ return -EINVAL;
+
+ err = apmf_if_call_store_buffer(dev, APMF_FUNC_GET_SYS_PARAMS, &params, sizeof(params));
+ if (err)
+ return err;
+
+ dev_dbg(dev->dev, "system params mask:0x%x flags:0x%x cmd_code:0x%x heartbeat:%d\n",
+ params.valid_mask,
+ params.flags,
+ params.command_code,
+ params.heartbeat_int);
+ params.flags = params.flags & params.valid_mask;
+ dev->hb_interval = params.heartbeat_int;
+
+ return 0;
+}
+
+void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
+{
+ acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
+
+ if (pmf_dev->hb_interval)
+ cancel_delayed_work_sync(&pmf_dev->heart_beat);
+
+ if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
+ is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
+ acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
+}
+
+int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)
+{
+ acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
+ acpi_status status;
+ int ret;
+
+ ret = apmf_if_verify_interface(pmf_dev);
+ if (ret) {
+ dev_err(pmf_dev->dev, "APMF verify interface failed :%d\n", ret);
+ goto out;
+ }
+
+ ret = apmf_get_system_params(pmf_dev);
+ if (ret) {
+ dev_err(pmf_dev->dev, "APMF apmf_get_system_params failed :%d\n", ret);
+ goto out;
+ }
+
+ if (pmf_dev->hb_interval) {
+ /* send heartbeats only if the interval is not zero */
+ INIT_DELAYED_WORK(&pmf_dev->heart_beat, apmf_sbios_heartbeat_notify);
+ schedule_delayed_work(&pmf_dev->heart_beat, 0);
+ }
+
+ /* Install the APMF Notify handler */
+ if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
+ is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS)) {
+ status = acpi_install_notify_handler(ahandle,
+ ACPI_ALL_NOTIFY,
+ apmf_event_handler, pmf_dev);
+ if (ACPI_FAILURE(status)) {
+ dev_err(pmf_dev->dev, "failed to install notify handler\n");
+ return -ENODEV;
+ }
+ /* Call the handler once manually to catch up with possibly missed notifies. */
+ apmf_event_handler(ahandle, 0, pmf_dev);
+ }
+
+out:
+ return ret;
+}
diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
new file mode 100644
index 000000000000..644af42e07cf
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/auto-mode.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/workqueue.h>
+#include "pmf.h"
+
+static struct auto_mode_mode_config config_store;
+static const char *state_as_str(unsigned int state);
+
+static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
+ struct auto_mode_mode_config *table)
+{
+ struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
+
+ amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
+ pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
+ pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
+
+ if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
+ apmf_update_fan_idx(dev, config_store.mode_set[idx].fan_control.manual,
+ config_store.mode_set[idx].fan_control.fan_id);
+}
+
+static int amd_pmf_get_moving_avg(struct amd_pmf_dev *pdev, int socket_power)
+{
+ int i, total = 0;
+
+ if (pdev->socket_power_history_idx == -1) {
+ for (i = 0; i < AVG_SAMPLE_SIZE; i++)
+ pdev->socket_power_history[i] = socket_power;
+ }
+
+ pdev->socket_power_history_idx = (pdev->socket_power_history_idx + 1) % AVG_SAMPLE_SIZE;
+ pdev->socket_power_history[pdev->socket_power_history_idx] = socket_power;
+
+ for (i = 0; i < AVG_SAMPLE_SIZE; i++)
+ total += pdev->socket_power_history[i];
+
+ return total / AVG_SAMPLE_SIZE;
+}
+
+void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms)
+{
+ int avg_power = 0;
+ bool update = false;
+ int i, j;
+
+ /* Get the average moving average computed by auto mode algorithm */
+ avg_power = amd_pmf_get_moving_avg(dev, socket_power);
+
+ for (i = 0; i < AUTO_TRANSITION_MAX; i++) {
+ if ((config_store.transition[i].shifting_up && avg_power >=
+ config_store.transition[i].power_threshold) ||
+ (!config_store.transition[i].shifting_up && avg_power <=
+ config_store.transition[i].power_threshold)) {
+ if (config_store.transition[i].timer <
+ config_store.transition[i].time_constant)
+ config_store.transition[i].timer += time_elapsed_ms;
+ } else {
+ config_store.transition[i].timer = 0;
+ }
+
+ if (config_store.transition[i].timer >=
+ config_store.transition[i].time_constant &&
+ !config_store.transition[i].applied) {
+ config_store.transition[i].applied = true;
+ update = true;
+ } else if (config_store.transition[i].timer <=
+ config_store.transition[i].time_constant &&
+ config_store.transition[i].applied) {
+ config_store.transition[i].applied = false;
+ update = true;
+ }
+ }
+
+ dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
+ state_as_str(config_store.current_mode));
+
+ if (update) {
+ for (j = 0; j < AUTO_TRANSITION_MAX; j++) {
+ /* Apply the mode with highest priority indentified */
+ if (config_store.transition[j].applied) {
+ if (config_store.current_mode !=
+ config_store.transition[j].target_mode) {
+ config_store.current_mode =
+ config_store.transition[j].target_mode;
+ dev_dbg(dev->dev, "[AUTO_MODE] moving to mode:%s\n",
+ state_as_str(config_store.current_mode));
+ amd_pmf_set_automode(dev, config_store.current_mode, NULL);
+ }
+ break;
+ }
+ }
+ }
+}
+
+void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event)
+{
+ int mode = config_store.current_mode;
+
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
+ is_cql_event ? AUTO_PERFORMANCE_ON_LAP : AUTO_PERFORMANCE;
+
+ if ((mode == AUTO_PERFORMANCE || mode == AUTO_PERFORMANCE_ON_LAP) &&
+ mode != config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode) {
+ mode = config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode;
+ amd_pmf_set_automode(dev, mode, NULL);
+ }
+ dev_dbg(dev->dev, "updated CQL thermals\n");
+}
+
+static void amd_pmf_get_power_threshold(void)
+{
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold =
+ config_store.mode_set[AUTO_BALANCE].power_floor -
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta;
+
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold =
+ config_store.mode_set[AUTO_BALANCE].power_floor -
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta;
+
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_threshold =
+ config_store.mode_set[AUTO_QUIET].power_floor -
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta;
+
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_threshold =
+ config_store.mode_set[AUTO_PERFORMANCE].power_floor -
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta;
+}
+
+static const char *state_as_str(unsigned int state)
+{
+ switch (state) {
+ case AUTO_QUIET:
+ return "QUIET";
+ case AUTO_BALANCE:
+ return "BALANCED";
+ case AUTO_PERFORMANCE_ON_LAP:
+ return "ON_LAP";
+ case AUTO_PERFORMANCE:
+ return "PERFORMANCE";
+ default:
+ return "Unknown Auto Mode State";
+ }
+}
+
+static void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev)
+{
+ struct apmf_auto_mode output;
+ struct power_table_control *pwr_ctrl;
+ int i;
+
+ apmf_get_auto_mode_def(dev, &output);
+ /* time constant */
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].time_constant =
+ output.balanced_to_quiet;
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant =
+ output.balanced_to_perf;
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant =
+ output.quiet_to_balanced;
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant =
+ output.perf_to_balanced;
+
+ /* power floor */
+ config_store.mode_set[AUTO_QUIET].power_floor = output.pfloor_quiet;
+ config_store.mode_set[AUTO_BALANCE].power_floor = output.pfloor_balanced;
+ config_store.mode_set[AUTO_PERFORMANCE].power_floor = output.pfloor_perf;
+ config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_floor = output.pfloor_perf;
+
+ /* Power delta for mode change */
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta =
+ output.pd_balanced_to_quiet;
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta =
+ output.pd_balanced_to_perf;
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta =
+ output.pd_quiet_to_balanced;
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta =
+ output.pd_perf_to_balanced;
+
+ /* Power threshold */
+ amd_pmf_get_power_threshold();
+
+ /* skin temperature limits */
+ pwr_ctrl = &config_store.mode_set[AUTO_QUIET].power_control;
+ pwr_ctrl->spl = output.spl_quiet;
+ pwr_ctrl->sppt = output.sppt_quiet;
+ pwr_ctrl->fppt = output.fppt_quiet;
+ pwr_ctrl->sppt_apu_only = output.sppt_apu_only_quiet;
+ pwr_ctrl->stt_min = output.stt_min_limit_quiet;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_quiet;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_quiet;
+
+ pwr_ctrl = &config_store.mode_set[AUTO_BALANCE].power_control;
+ pwr_ctrl->spl = output.spl_balanced;
+ pwr_ctrl->sppt = output.sppt_balanced;
+ pwr_ctrl->fppt = output.fppt_balanced;
+ pwr_ctrl->sppt_apu_only = output.sppt_apu_only_balanced;
+ pwr_ctrl->stt_min = output.stt_min_limit_balanced;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_balanced;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_balanced;
+
+ pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE].power_control;
+ pwr_ctrl->spl = output.spl_perf;
+ pwr_ctrl->sppt = output.sppt_perf;
+ pwr_ctrl->fppt = output.fppt_perf;
+ pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf;
+ pwr_ctrl->stt_min = output.stt_min_limit_perf;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf;
+
+ pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_control;
+ pwr_ctrl->spl = output.spl_perf_on_lap;
+ pwr_ctrl->sppt = output.sppt_perf_on_lap;
+ pwr_ctrl->fppt = output.fppt_perf_on_lap;
+ pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf_on_lap;
+ pwr_ctrl->stt_min = output.stt_min_limit_perf_on_lap;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf_on_lap;
+ pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf_on_lap;
+
+ /* Fan ID */
+ config_store.mode_set[AUTO_QUIET].fan_control.fan_id = output.fan_id_quiet;
+ config_store.mode_set[AUTO_BALANCE].fan_control.fan_id = output.fan_id_balanced;
+ config_store.mode_set[AUTO_PERFORMANCE].fan_control.fan_id = output.fan_id_perf;
+ config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].fan_control.fan_id =
+ output.fan_id_perf;
+
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].target_mode = AUTO_QUIET;
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
+ AUTO_PERFORMANCE;
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].target_mode =
+ AUTO_BALANCE;
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].target_mode =
+ AUTO_BALANCE;
+
+ config_store.transition[AUTO_TRANSITION_TO_QUIET].shifting_up = false;
+ config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].shifting_up = true;
+ config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].shifting_up = true;
+ config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].shifting_up =
+ false;
+
+ for (i = 0 ; i < AUTO_MODE_MAX ; i++) {
+ if (config_store.mode_set[i].fan_control.fan_id == FAN_INDEX_AUTO)
+ config_store.mode_set[i].fan_control.manual = false;
+ else
+ config_store.mode_set[i].fan_control.manual = true;
+ }
+
+ /* set to initial default values */
+ config_store.current_mode = AUTO_BALANCE;
+ dev->socket_power_history_idx = -1;
+}
+
+int amd_pmf_reset_amt(struct amd_pmf_dev *dev)
+{
+ /*
+ * OEM BIOS implementation guide says that if the auto mode is enabled
+ * the platform_profile registration shall be done by the OEM driver.
+ * There could be cases where both static slider and auto mode BIOS
+ * functions are enabled, in that case enable static slider updates
+ * only if it advertised as supported.
+ */
+
+ if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
+ int mode = amd_pmf_get_pprof_modes(dev);
+
+ if (mode < 0)
+ return mode;
+
+ dev_dbg(dev->dev, "resetting AMT thermals\n");
+ amd_pmf_update_slider(dev, SLIDER_OP_SET, mode, NULL);
+ }
+ return 0;
+}
+
+void amd_pmf_handle_amt(struct amd_pmf_dev *dev)
+{
+ amd_pmf_set_automode(dev, config_store.current_mode, NULL);
+}
+
+void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev)
+{
+ cancel_delayed_work_sync(&dev->work_buffer);
+}
+
+void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev)
+{
+ amd_pmf_load_defaults_auto_mode(dev);
+ /* update the thermal limits for Automode */
+ amd_pmf_set_automode(dev, config_store.current_mode, NULL);
+ amd_pmf_init_metrics_table(dev);
+}
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
new file mode 100644
index 000000000000..a675ca969331
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include "pmf.h"
+
+/* PMF-SMU communication registers */
+#define AMD_PMF_REGISTER_MESSAGE 0xA18
+#define AMD_PMF_REGISTER_RESPONSE 0xA78
+#define AMD_PMF_REGISTER_ARGUMENT 0xA58
+
+/* Base address of SMU for mapping physical address to virtual address */
+#define AMD_PMF_SMU_INDEX_ADDRESS 0xB8
+#define AMD_PMF_SMU_INDEX_DATA 0xBC
+#define AMD_PMF_MAPPING_SIZE 0x01000
+#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
+#define AMD_PMF_BASE_ADDR_LO 0x13B102E8
+#define AMD_PMF_BASE_ADDR_HI 0x13B102EC
+#define AMD_PMF_BASE_ADDR_LO_MASK GENMASK(15, 0)
+#define AMD_PMF_BASE_ADDR_HI_MASK GENMASK(31, 20)
+
+/* SMU Response Codes */
+#define AMD_PMF_RESULT_OK 0x01
+#define AMD_PMF_RESULT_CMD_REJECT_BUSY 0xFC
+#define AMD_PMF_RESULT_CMD_REJECT_PREREQ 0xFD
+#define AMD_PMF_RESULT_CMD_UNKNOWN 0xFE
+#define AMD_PMF_RESULT_FAILED 0xFF
+
+/* List of supported CPU ids */
+#define AMD_CPU_ID_RMB 0x14b5
+#define AMD_CPU_ID_PS 0x14e8
+
+#define PMF_MSG_DELAY_MIN_US 50
+#define RESPONSE_REGISTER_LOOP_MAX 20000
+
+#define DELAY_MIN_US 2000
+#define DELAY_MAX_US 3000
+
+/* override Metrics Table sample size time (in ms) */
+static 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)");
+
+/* Force load on supported older platforms */
+static bool force_load;
+module_param(force_load, bool, 0444);
+MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
+
+static int current_power_limits_show(struct seq_file *seq, void *unused)
+{
+ struct amd_pmf_dev *dev = seq->private;
+ struct amd_pmf_static_slider_granular table;
+ int mode, src = 0;
+
+ mode = amd_pmf_get_pprof_modes(dev);
+ if (mode < 0)
+ return mode;
+
+ src = amd_pmf_get_power_source();
+ amd_pmf_update_slider(dev, SLIDER_OP_GET, mode, &table);
+ seq_printf(seq, "spl:%u fppt:%u sppt:%u sppt_apu_only:%u stt_min:%u stt[APU]:%u stt[HS2]: %u\n",
+ table.prop[src][mode].spl,
+ table.prop[src][mode].fppt,
+ table.prop[src][mode].sppt,
+ table.prop[src][mode].sppt_apu_only,
+ table.prop[src][mode].stt_min,
+ table.prop[src][mode].stt_skin_temp[STT_TEMP_APU],
+ table.prop[src][mode].stt_skin_temp[STT_TEMP_HS2]);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(current_power_limits);
+
+static void amd_pmf_dbgfs_unregister(struct amd_pmf_dev *dev)
+{
+ debugfs_remove_recursive(dev->dbgfs_dir);
+}
+
+static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev)
+{
+ dev->dbgfs_dir = debugfs_create_dir("amd_pmf", NULL);
+ debugfs_create_file("current_power_limits", 0644, dev->dbgfs_dir, dev,
+ &current_power_limits_fops);
+}
+
+int amd_pmf_get_power_source(void)
+{
+ if (power_supply_is_system_supplied() > 0)
+ return POWER_SOURCE_AC;
+ else
+ return POWER_SOURCE_DC;
+}
+
+static 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;
+
+ mutex_lock(&dev->update_mutex);
+ /* Transfer table contents */
+ memset(dev->buf, 0, sizeof(dev->m_table));
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, 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);
+ }
+
+ dev->start_time = ktime_to_ms(ktime_get());
+ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
+ mutex_unlock(&dev->update_mutex);
+}
+
+static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
+{
+ return ioread32(dev->regbase + reg_offset);
+}
+
+static inline void amd_pmf_reg_write(struct amd_pmf_dev *dev, int reg_offset, u32 val)
+{
+ iowrite32(val, dev->regbase + reg_offset);
+}
+
+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);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
+
+ value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
+
+ value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
+ dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
+}
+
+int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
+{
+ int rc;
+ u32 val;
+
+ mutex_lock(&dev->lock);
+
+ /* Wait until we get a valid response */
+ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ val, val != 0, PMF_MSG_DELAY_MIN_US,
+ PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
+ if (rc) {
+ dev_err(dev->dev, "failed to talk to SMU\n");
+ goto out_unlock;
+ }
+
+ /* Write zero to response register */
+ amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
+
+ /* Write argument into argument register */
+ amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
+
+ /* Write message ID to message ID register */
+ amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
+
+ /* Wait until we get a valid response */
+ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+ val, val != 0, PMF_MSG_DELAY_MIN_US,
+ PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
+ if (rc) {
+ dev_err(dev->dev, "SMU response timed out\n");
+ goto out_unlock;
+ }
+
+ switch (val) {
+ case AMD_PMF_RESULT_OK:
+ 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);
+ }
+ break;
+ case AMD_PMF_RESULT_CMD_REJECT_BUSY:
+ dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val);
+ rc = -EBUSY;
+ goto out_unlock;
+ case AMD_PMF_RESULT_CMD_UNKNOWN:
+ dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val);
+ rc = -EINVAL;
+ goto out_unlock;
+ case AMD_PMF_RESULT_CMD_REJECT_PREREQ:
+ case AMD_PMF_RESULT_FAILED:
+ default:
+ dev_err(dev->dev, "SMU cmd failed. err: 0x%x\n", val);
+ rc = -EIO;
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&dev->lock);
+ amd_pmf_dump_registers(dev);
+ 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) },
+ { }
+};
+
+int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
+{
+ u64 phys_addr;
+ u32 hi, low;
+
+ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
+
+ /* Get Metrics Table Address */
+ dev->buf = kzalloc(sizeof(dev->m_table), 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, 0, hi, NULL);
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, 0, low, NULL);
+
+ /*
+ * 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 void amd_pmf_init_features(struct amd_pmf_dev *dev)
+{
+ /* Enable Static Slider */
+ if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
+ amd_pmf_init_sps(dev);
+ dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
+ }
+
+ /* Enable Auto Mode */
+ if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
+ amd_pmf_init_auto_mode(dev);
+ dev_dbg(dev->dev, "Auto Mode Init done\n");
+ }
+}
+
+static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
+{
+ if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
+ amd_pmf_deinit_sps(dev);
+
+ if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE))
+ amd_pmf_deinit_auto_mode(dev);
+}
+
+static const struct acpi_device_id amd_pmf_acpi_ids[] = {
+ {"AMDI0100", 0x100},
+ {"AMDI0102", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
+
+static int amd_pmf_probe(struct platform_device *pdev)
+{
+ const struct acpi_device_id *id;
+ struct amd_pmf_dev *dev;
+ struct pci_dev *rdev;
+ u32 base_addr_lo;
+ u32 base_addr_hi;
+ u64 base_addr;
+ u32 val;
+ int err;
+
+ id = acpi_match_device(amd_pmf_acpi_ids, &pdev->dev);
+ if (!id)
+ return -ENODEV;
+
+ if (id->driver_data == 0x100 && !force_load)
+ return -ENODEV;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->dev = &pdev->dev;
+
+ rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
+ if (!rdev || !pci_match_id(pmf_pci_ids, rdev)) {
+ pci_dev_put(rdev);
+ return -ENODEV;
+ }
+
+ dev->cpu_id = rdev->device;
+ err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_LO);
+ if (err) {
+ dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
+ pci_dev_put(rdev);
+ return pcibios_err_to_errno(err);
+ }
+
+ err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
+ if (err) {
+ pci_dev_put(rdev);
+ return pcibios_err_to_errno(err);
+ }
+
+ base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
+
+ err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_HI);
+ if (err) {
+ dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
+ pci_dev_put(rdev);
+ return pcibios_err_to_errno(err);
+ }
+
+ err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
+ if (err) {
+ pci_dev_put(rdev);
+ return pcibios_err_to_errno(err);
+ }
+
+ base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
+ pci_dev_put(rdev);
+ base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
+
+ dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMF_BASE_ADDR_OFFSET,
+ AMD_PMF_MAPPING_SIZE);
+ if (!dev->regbase)
+ return -ENOMEM;
+
+ apmf_acpi_init(dev);
+ platform_set_drvdata(pdev, dev);
+ amd_pmf_init_features(dev);
+ amd_pmf_dbgfs_register(dev);
+
+ mutex_init(&dev->lock);
+ mutex_init(&dev->update_mutex);
+ dev_info(dev->dev, "registered PMF device successfully\n");
+
+ return 0;
+}
+
+static int amd_pmf_remove(struct platform_device *pdev)
+{
+ struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
+
+ mutex_destroy(&dev->lock);
+ mutex_destroy(&dev->update_mutex);
+ amd_pmf_deinit_features(dev);
+ apmf_acpi_deinit(dev);
+ amd_pmf_dbgfs_unregister(dev);
+ kfree(dev->buf);
+ return 0;
+}
+
+static struct platform_driver amd_pmf_driver = {
+ .driver = {
+ .name = "amd-pmf",
+ .acpi_match_table = amd_pmf_acpi_ids,
+ },
+ .probe = amd_pmf_probe,
+ .remove = amd_pmf_remove,
+};
+module_platform_driver(amd_pmf_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Platform Management Framework Driver");
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
new file mode 100644
index 000000000000..0a72a395c2ef
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -0,0 +1,327 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#ifndef PMF_H
+#define PMF_H
+
+#include <linux/acpi.h>
+#include <linux/platform_profile.h>
+
+/* APMF Functions */
+#define APMF_FUNC_VERIFY_INTERFACE 0
+#define APMF_FUNC_GET_SYS_PARAMS 1
+#define APMF_FUNC_SBIOS_REQUESTS 2
+#define APMF_FUNC_SBIOS_HEARTBEAT 4
+#define APMF_FUNC_AUTO_MODE 5
+#define APMF_FUNC_SET_FAN_IDX 7
+#define APMF_FUNC_STATIC_SLIDER_GRANULAR 9
+
+/* Message Definitions */
+#define SET_SPL 0x03 /* SPL: Sustained Power Limit */
+#define SET_SPPT 0x05 /* SPPT: Slow Package Power Tracking */
+#define SET_FPPT 0x07 /* FPPT: Fast Package Power Tracking */
+#define GET_SPL 0x0B
+#define GET_SPPT 0x0D
+#define GET_FPPT 0x0F
+#define SET_DRAM_ADDR_HIGH 0x14
+#define SET_DRAM_ADDR_LOW 0x15
+#define SET_TRANSFER_TABLE 0x16
+#define SET_STT_MIN_LIMIT 0x18 /* STT: Skin Temperature Tracking */
+#define SET_STT_LIMIT_APU 0x19
+#define SET_STT_LIMIT_HS2 0x1A
+#define SET_SPPT_APU_ONLY 0x1D
+#define GET_SPPT_APU_ONLY 0x1E
+#define GET_STT_MIN_LIMIT 0x1F
+#define GET_STT_LIMIT_APU 0x20
+#define GET_STT_LIMIT_HS2 0x21
+
+/* Fan Index for Auto Mode */
+#define FAN_INDEX_AUTO 0xFFFFFFFF
+
+#define ARG_NONE 0
+#define AVG_SAMPLE_SIZE 3
+
+/* AMD PMF BIOS interfaces */
+struct apmf_verify_interface {
+ u16 size;
+ u16 version;
+ u32 notification_mask;
+ u32 supported_functions;
+} __packed;
+
+struct apmf_system_params {
+ u16 size;
+ u32 valid_mask;
+ u32 flags;
+ u8 command_code;
+ u32 heartbeat_int;
+} __packed;
+
+struct apmf_sbios_req {
+ u16 size;
+ u32 pending_req;
+ u8 rsd;
+ u8 cql_event;
+ u8 amt_event;
+ u32 fppt;
+ u32 sppt;
+ u32 fppt_apu_only;
+ u32 spl;
+ u32 stt_min_limit;
+ u8 skin_temp_apu;
+ u8 skin_temp_hs2;
+} __packed;
+
+struct apmf_fan_idx {
+ u16 size;
+ u8 fan_ctl_mode;
+ u32 fan_ctl_idx;
+} __packed;
+
+struct smu_pmf_metrics {
+ u16 gfxclk_freq; /* in MHz */
+ u16 socclk_freq; /* in MHz */
+ u16 vclk_freq; /* in MHz */
+ u16 dclk_freq; /* in MHz */
+ u16 memclk_freq; /* in MHz */
+ u16 spare;
+ u16 gfx_activity; /* in Centi */
+ u16 uvd_activity; /* in Centi */
+ u16 voltage[2]; /* in mV */
+ u16 currents[2]; /* in mA */
+ u16 power[2];/* in mW */
+ u16 core_freq[8]; /* in MHz */
+ u16 core_power[8]; /* in mW */
+ u16 core_temp[8]; /* in centi-Celsius */
+ u16 l3_freq; /* in MHz */
+ u16 l3_temp; /* in centi-Celsius */
+ u16 gfx_temp; /* in centi-Celsius */
+ u16 soc_temp; /* in centi-Celsius */
+ u16 throttler_status;
+ u16 current_socketpower; /* in mW */
+ u16 stapm_orig_limit; /* in W */
+ u16 stapm_cur_limit; /* in W */
+ u32 apu_power; /* in mW */
+ u32 dgpu_power; /* in mW */
+ u16 vdd_tdc_val; /* in mA */
+ u16 soc_tdc_val; /* in mA */
+ u16 vdd_edc_val; /* in mA */
+ u16 soc_edcv_al; /* in mA */
+ u16 infra_cpu_maxfreq; /* in MHz */
+ u16 infra_gfx_maxfreq; /* in MHz */
+ u16 skin_temp; /* in centi-Celsius */
+ u16 device_state;
+} __packed;
+
+enum amd_stt_skin_temp {
+ STT_TEMP_APU,
+ STT_TEMP_HS2,
+ STT_TEMP_COUNT,
+};
+
+enum amd_slider_op {
+ SLIDER_OP_GET,
+ SLIDER_OP_SET,
+};
+
+enum power_source {
+ POWER_SOURCE_AC,
+ POWER_SOURCE_DC,
+ POWER_SOURCE_MAX,
+};
+
+enum power_modes {
+ POWER_MODE_PERFORMANCE,
+ POWER_MODE_BALANCED_POWER,
+ POWER_MODE_POWER_SAVER,
+ POWER_MODE_MAX,
+};
+
+struct amd_pmf_dev {
+ void __iomem *regbase;
+ void __iomem *smu_virt_addr;
+ void *buf;
+ u32 base_addr;
+ u32 cpu_id;
+ struct device *dev;
+ struct mutex lock; /* protects the PMF interface */
+ u32 supported_func;
+ enum platform_profile_option current_profile;
+ struct platform_profile_handler pprof;
+ struct dentry *dbgfs_dir;
+ int hb_interval; /* SBIOS heartbeat interval */
+ struct delayed_work heart_beat;
+ struct smu_pmf_metrics m_table;
+ struct delayed_work work_buffer;
+ ktime_t start_time;
+ int socket_power_history[AVG_SAMPLE_SIZE];
+ int socket_power_history_idx;
+ bool amt_enabled;
+ struct mutex update_mutex; /* protects race between ACPI handler and metrics thread */
+};
+
+struct apmf_sps_prop_granular {
+ u32 fppt;
+ u32 sppt;
+ u32 sppt_apu_only;
+ u32 spl;
+ u32 stt_min;
+ u8 stt_skin_temp[STT_TEMP_COUNT];
+ u32 fan_id;
+} __packed;
+
+/* Static Slider */
+struct apmf_static_slider_granular_output {
+ u16 size;
+ struct apmf_sps_prop_granular prop[POWER_SOURCE_MAX * POWER_MODE_MAX];
+} __packed;
+
+struct amd_pmf_static_slider_granular {
+ u16 size;
+ struct apmf_sps_prop_granular prop[POWER_SOURCE_MAX][POWER_MODE_MAX];
+};
+
+struct fan_table_control {
+ bool manual;
+ unsigned long fan_id;
+};
+
+struct power_table_control {
+ u32 spl;
+ u32 sppt;
+ u32 fppt;
+ u32 sppt_apu_only;
+ u32 stt_min;
+ u32 stt_skin_temp[STT_TEMP_COUNT];
+ u32 reserved[16];
+};
+
+/* Auto Mode Layer */
+enum auto_mode_transition_priority {
+ AUTO_TRANSITION_TO_PERFORMANCE, /* Any other mode to Performance Mode */
+ AUTO_TRANSITION_FROM_QUIET_TO_BALANCE, /* Quiet Mode to Balance Mode */
+ AUTO_TRANSITION_TO_QUIET, /* Any other mode to Quiet Mode */
+ AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE, /* Performance Mode to Balance Mode */
+ AUTO_TRANSITION_MAX,
+};
+
+enum auto_mode_mode {
+ AUTO_QUIET,
+ AUTO_BALANCE,
+ AUTO_PERFORMANCE_ON_LAP,
+ AUTO_PERFORMANCE,
+ AUTO_MODE_MAX,
+};
+
+struct auto_mode_trans_params {
+ u32 time_constant; /* minimum time required to switch to next mode */
+ u32 power_delta; /* delta power to shift mode */
+ u32 power_threshold;
+ u32 timer; /* elapsed time. if timer > TimeThreshold, it will move to next mode */
+ u32 applied;
+ enum auto_mode_mode target_mode;
+ u32 shifting_up;
+};
+
+struct auto_mode_mode_settings {
+ struct power_table_control power_control;
+ struct fan_table_control fan_control;
+ u32 power_floor;
+};
+
+struct auto_mode_mode_config {
+ struct auto_mode_trans_params transition[AUTO_TRANSITION_MAX];
+ struct auto_mode_mode_settings mode_set[AUTO_MODE_MAX];
+ enum auto_mode_mode current_mode;
+};
+
+struct apmf_auto_mode {
+ u16 size;
+ /* time constant */
+ u32 balanced_to_perf;
+ u32 perf_to_balanced;
+ u32 quiet_to_balanced;
+ u32 balanced_to_quiet;
+ /* power floor */
+ u32 pfloor_perf;
+ u32 pfloor_balanced;
+ u32 pfloor_quiet;
+ /* Power delta for mode change */
+ u32 pd_balanced_to_perf;
+ u32 pd_perf_to_balanced;
+ u32 pd_quiet_to_balanced;
+ u32 pd_balanced_to_quiet;
+ /* skin temperature limits */
+ u8 stt_apu_perf_on_lap; /* CQL ON */
+ u8 stt_hs2_perf_on_lap; /* CQL ON */
+ u8 stt_apu_perf;
+ u8 stt_hs2_perf;
+ u8 stt_apu_balanced;
+ u8 stt_hs2_balanced;
+ u8 stt_apu_quiet;
+ u8 stt_hs2_quiet;
+ u32 stt_min_limit_perf_on_lap; /* CQL ON */
+ u32 stt_min_limit_perf;
+ u32 stt_min_limit_balanced;
+ u32 stt_min_limit_quiet;
+ /* SPL based */
+ u32 fppt_perf_on_lap; /* CQL ON */
+ u32 sppt_perf_on_lap; /* CQL ON */
+ u32 spl_perf_on_lap; /* CQL ON */
+ u32 sppt_apu_only_perf_on_lap; /* CQL ON */
+ u32 fppt_perf;
+ u32 sppt_perf;
+ u32 spl_perf;
+ u32 sppt_apu_only_perf;
+ u32 fppt_balanced;
+ u32 sppt_balanced;
+ u32 spl_balanced;
+ u32 sppt_apu_only_balanced;
+ u32 fppt_quiet;
+ u32 sppt_quiet;
+ u32 spl_quiet;
+ u32 sppt_apu_only_quiet;
+ /* Fan ID */
+ u32 fan_id_perf;
+ u32 fan_id_balanced;
+ u32 fan_id_quiet;
+} __packed;
+
+/* Core Layer */
+int apmf_acpi_init(struct amd_pmf_dev *pmf_dev);
+void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev);
+int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index);
+int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data);
+int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev);
+int amd_pmf_get_power_source(void);
+
+/* SPS Layer */
+int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
+void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
+ struct amd_pmf_static_slider_granular *table);
+int amd_pmf_init_sps(struct amd_pmf_dev *dev);
+void amd_pmf_deinit_sps(struct amd_pmf_dev *dev);
+int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
+ struct apmf_static_slider_granular_output *output);
+
+
+int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx);
+
+/* Auto Mode Layer */
+int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data);
+void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
+void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
+void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
+int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
+
+void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
+int amd_pmf_reset_amt(struct amd_pmf_dev *dev);
+void amd_pmf_handle_amt(struct amd_pmf_dev *dev);
+#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c
new file mode 100644
index 000000000000..dba7e36962dc
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/sps.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Platform Management Framework (PMF) Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#include "pmf.h"
+
+static struct amd_pmf_static_slider_granular config_store;
+
+static void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev)
+{
+ struct apmf_static_slider_granular_output output;
+ int i, j, idx = 0;
+
+ memset(&config_store, 0, sizeof(config_store));
+ apmf_get_static_slider_granular(dev, &output);
+
+ for (i = 0; i < POWER_SOURCE_MAX; i++) {
+ for (j = 0; j < POWER_MODE_MAX; j++) {
+ config_store.prop[i][j].spl = output.prop[idx].spl;
+ config_store.prop[i][j].sppt = output.prop[idx].sppt;
+ config_store.prop[i][j].sppt_apu_only =
+ output.prop[idx].sppt_apu_only;
+ config_store.prop[i][j].fppt = output.prop[idx].fppt;
+ config_store.prop[i][j].stt_min = output.prop[idx].stt_min;
+ config_store.prop[i][j].stt_skin_temp[STT_TEMP_APU] =
+ output.prop[idx].stt_skin_temp[STT_TEMP_APU];
+ config_store.prop[i][j].stt_skin_temp[STT_TEMP_HS2] =
+ output.prop[idx].stt_skin_temp[STT_TEMP_HS2];
+ config_store.prop[i][j].fan_id = output.prop[idx].fan_id;
+ idx++;
+ }
+ }
+}
+
+void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
+ struct amd_pmf_static_slider_granular *table)
+{
+ int src = amd_pmf_get_power_source();
+
+ if (op == SLIDER_OP_SET) {
+ amd_pmf_send_cmd(dev, SET_SPL, false, config_store.prop[src][idx].spl, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, false, config_store.prop[src][idx].fppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, false, config_store.prop[src][idx].sppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false,
+ config_store.prop[src][idx].sppt_apu_only, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false,
+ config_store.prop[src][idx].stt_min, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
+ config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU], NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
+ config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2], NULL);
+ } else if (op == SLIDER_OP_GET) {
+ amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE, &table->prop[src][idx].spl);
+ amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE, &table->prop[src][idx].fppt);
+ amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE, &table->prop[src][idx].sppt);
+ amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
+ &table->prop[src][idx].sppt_apu_only);
+ amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
+ &table->prop[src][idx].stt_min);
+ amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
+ (u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_APU]);
+ amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
+ (u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_HS2]);
+ }
+}
+
+static int amd_pmf_profile_get(struct platform_profile_handler *pprof,
+ enum platform_profile_option *profile)
+{
+ struct amd_pmf_dev *pmf = container_of(pprof, struct amd_pmf_dev, pprof);
+
+ *profile = pmf->current_profile;
+ return 0;
+}
+
+int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf)
+{
+ int mode;
+
+ switch (pmf->current_profile) {
+ case PLATFORM_PROFILE_PERFORMANCE:
+ mode = POWER_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ mode = POWER_MODE_BALANCED_POWER;
+ break;
+ case PLATFORM_PROFILE_LOW_POWER:
+ mode = POWER_MODE_POWER_SAVER;
+ break;
+ default:
+ dev_err(pmf->dev, "Unknown Platform Profile.\n");
+ return -EOPNOTSUPP;
+ }
+
+ return mode;
+}
+
+static int amd_pmf_profile_set(struct platform_profile_handler *pprof,
+ enum platform_profile_option profile)
+{
+ struct amd_pmf_dev *pmf = container_of(pprof, struct amd_pmf_dev, pprof);
+ int mode;
+
+ pmf->current_profile = profile;
+ mode = amd_pmf_get_pprof_modes(pmf);
+ if (mode < 0)
+ return mode;
+
+ amd_pmf_update_slider(pmf, SLIDER_OP_SET, mode, NULL);
+ return 0;
+}
+
+int amd_pmf_init_sps(struct amd_pmf_dev *dev)
+{
+ int err;
+
+ dev->current_profile = PLATFORM_PROFILE_BALANCED;
+ amd_pmf_load_defaults_sps(dev);
+
+ dev->pprof.profile_get = amd_pmf_profile_get;
+ dev->pprof.profile_set = amd_pmf_profile_set;
+
+ /* Setup supported modes */
+ set_bit(PLATFORM_PROFILE_LOW_POWER, dev->pprof.choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, dev->pprof.choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, dev->pprof.choices);
+
+ /* Create platform_profile structure and register */
+ err = platform_profile_register(&dev->pprof);
+ if (err)
+ dev_err(dev->dev, "Failed to register SPS support, this is most likely an SBIOS bug: %d\n",
+ err);
+
+ return err;
+}
+
+void amd_pmf_deinit_sps(struct amd_pmf_dev *dev)
+{
+ platform_profile_remove();
+}
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index bbfed85051ee..613c45c9fbe3 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -43,7 +43,7 @@ MODULE_PARM_DESC(wapf, "WAPF value");
static int tablet_mode_sw = -1;
module_param(tablet_mode_sw, uint, 0444);
-MODULE_PARM_DESC(tablet_mode_sw, "Tablet mode detect: -1:auto 0:disable 1:kbd-dock 2:lid-flip");
+MODULE_PARM_DESC(tablet_mode_sw, "Tablet mode detect: -1:auto 0:disable 1:kbd-dock 2:lid-flip 3:lid-flip-rog");
static struct quirk_entry *quirks;
@@ -108,12 +108,17 @@ static struct quirk_entry quirk_asus_forceals = {
};
static struct quirk_entry quirk_asus_use_kbd_dock_devid = {
- .use_kbd_dock_devid = true,
+ .tablet_switch_mode = asus_wmi_kbd_dock_devid,
};
static struct quirk_entry quirk_asus_use_lid_flip_devid = {
.wmi_backlight_set_devstate = true,
- .use_lid_flip_devid = true,
+ .tablet_switch_mode = asus_wmi_lid_flip_devid,
+};
+
+static struct quirk_entry quirk_asus_tablet_mode = {
+ .wmi_backlight_set_devstate = true,
+ .tablet_switch_mode = asus_wmi_lid_flip_rog_devid,
};
static int dmi_matched(const struct dmi_system_id *dmi)
@@ -450,6 +455,15 @@ static const struct dmi_system_id asus_quirks[] = {
},
.driver_data = &quirk_asus_use_lid_flip_devid,
},
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS ROG FLOW X13",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "GV301Q"),
+ },
+ .driver_data = &quirk_asus_tablet_mode,
+ },
{},
};
@@ -469,20 +483,8 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
else
wapf = quirks->wapf;
- switch (tablet_mode_sw) {
- case 0:
- quirks->use_kbd_dock_devid = false;
- quirks->use_lid_flip_devid = false;
- break;
- case 1:
- quirks->use_kbd_dock_devid = true;
- quirks->use_lid_flip_devid = false;
- break;
- case 2:
- quirks->use_kbd_dock_devid = false;
- quirks->use_lid_flip_devid = true;
- break;
- }
+ if (tablet_mode_sw != -1)
+ quirks->tablet_switch_mode = tablet_mode_sw;
if (quirks->i8042_filter) {
ret = i8042_install_filter(quirks->i8042_filter);
@@ -554,12 +556,14 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
{ KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
{ KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+ { KE_KEY, 0xAE, { KEY_FN_F5 } }, /* Fn+F5 fan mode on 2020+ */
{ KE_KEY, 0xB3, { KEY_PROG4 } }, /* AURA */
{ KE_KEY, 0xB5, { KEY_CALC } },
{ KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
{ KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
{ KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */
{ KE_KEY, 0xFA, { KEY_PROG2 } }, /* Lid flip action */
+ { KE_KEY, 0xBD, { KEY_PROG2 } }, /* Lid flip action on ROG xflow laptops */
{ KE_END, 0},
};
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 434249ac47a5..d95170b7dba0 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -68,6 +68,7 @@ module_param(fnlock_default, bool, 0444);
#define NOTIFY_KBD_FBM 0x99
#define NOTIFY_KBD_TTP 0xae
#define NOTIFY_LID_FLIP 0xfa
+#define NOTIFY_LID_FLIP_ROG 0xbd
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
@@ -107,7 +108,7 @@ module_param(fnlock_default, bool, 0444);
#define WMI_EVENT_MASK 0xFFFF
#define FAN_CURVE_POINTS 8
-#define FAN_CURVE_BUF_LEN (FAN_CURVE_POINTS * 2)
+#define FAN_CURVE_BUF_LEN 32
#define FAN_CURVE_DEV_CPU 0x00
#define FAN_CURVE_DEV_GPU 0x01
/* Mask to determine if setting temperature or percentage */
@@ -221,19 +222,25 @@ struct asus_wmi {
struct asus_rfkill gps;
struct asus_rfkill uwb;
+ int tablet_switch_event_code;
+ u32 tablet_switch_dev_id;
+
enum fan_type fan_type;
+ enum fan_type gpu_fan_type;
int fan_pwm_mode;
+ int gpu_fan_pwm_mode;
int agfn_pwm;
bool fan_boost_mode_available;
u8 fan_boost_mode_mask;
u8 fan_boost_mode;
- bool egpu_enable_available; // 0 = enable
- bool egpu_enable;
-
+ bool egpu_enable_available;
bool dgpu_disable_available;
- bool dgpu_disable;
+ bool gpu_mux_mode_available;
+
+ bool kbd_rgb_mode_available;
+ bool kbd_rgb_state_available;
bool throttle_thermal_policy_available;
u8 throttle_thermal_policy_mode;
@@ -249,7 +256,6 @@ struct asus_wmi {
bool battery_rsoc_available;
bool panel_overdrive_available;
- bool panel_overdrive;
struct hotplug_slot hotplug_slot;
struct mutex hotplug_lock;
@@ -486,10 +492,28 @@ static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
}
/* Input **********************************************************************/
+static void asus_wmi_tablet_sw_init(struct asus_wmi *asus, u32 dev_id, int event_code)
+{
+ struct device *dev = &asus->platform_device->dev;
+ int result;
+
+ result = asus_wmi_get_devstate_simple(asus, dev_id);
+ if (result >= 0) {
+ input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
+ input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
+ asus->tablet_switch_dev_id = dev_id;
+ asus->tablet_switch_event_code = event_code;
+ } else if (result == -ENODEV) {
+ dev_err(dev, "This device has tablet-mode-switch quirk but got ENODEV checking it. This is a bug.");
+ } else {
+ dev_err(dev, "Error checking for tablet-mode-switch: %d\n", result);
+ }
+}
static int asus_wmi_input_init(struct asus_wmi *asus)
{
- int err, result;
+ struct device *dev = &asus->platform_device->dev;
+ int err;
asus->inputdev = input_allocate_device();
if (!asus->inputdev)
@@ -498,35 +522,25 @@ static int asus_wmi_input_init(struct asus_wmi *asus)
asus->inputdev->name = asus->driver->input_name;
asus->inputdev->phys = asus->driver->input_phys;
asus->inputdev->id.bustype = BUS_HOST;
- asus->inputdev->dev.parent = &asus->platform_device->dev;
+ asus->inputdev->dev.parent = dev;
set_bit(EV_REP, asus->inputdev->evbit);
err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL);
if (err)
goto err_free_dev;
- if (asus->driver->quirks->use_kbd_dock_devid) {
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_KBD_DOCK);
- if (result >= 0) {
- input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
- input_report_switch(asus->inputdev, SW_TABLET_MODE, !result);
- } else if (result != -ENODEV) {
- pr_err("Error checking for keyboard-dock: %d\n", result);
- }
- }
-
- if (asus->driver->quirks->use_lid_flip_devid) {
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
- if (result < 0)
- asus->driver->quirks->use_lid_flip_devid = 0;
- if (result >= 0) {
- input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
- input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
- } else if (result == -ENODEV) {
- pr_err("This device has lid_flip quirk but got ENODEV checking it. This is a bug.");
- } else {
- pr_err("Error checking for lid-flip: %d\n", result);
- }
+ switch (asus->driver->quirks->tablet_switch_mode) {
+ case asus_wmi_no_tablet_switch:
+ break;
+ case asus_wmi_kbd_dock_devid:
+ asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_KBD_DOCK, NOTIFY_KBD_DOCK_CHANGE);
+ break;
+ case asus_wmi_lid_flip_devid:
+ asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_LID_FLIP, NOTIFY_LID_FLIP);
+ break;
+ case asus_wmi_lid_flip_rog_devid:
+ asus_wmi_tablet_sw_init(asus, ASUS_WMI_DEVID_LID_FLIP_ROG, NOTIFY_LID_FLIP_ROG);
+ break;
}
err = input_register_device(asus->inputdev);
@@ -550,10 +564,14 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
/* Tablet mode ****************************************************************/
-static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus)
+static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
{
- int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
+ int result;
+ if (!asus->tablet_switch_dev_id)
+ return;
+
+ result = asus_wmi_get_devstate_simple(asus, asus->tablet_switch_dev_id);
if (result >= 0) {
input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
input_sync(asus->inputdev);
@@ -561,179 +579,267 @@ static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus)
}
/* dGPU ********************************************************************/
-static int dgpu_disable_check_present(struct asus_wmi *asus)
+static ssize_t dgpu_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
- u32 result;
- int err;
-
- asus->dgpu_disable_available = false;
-
- err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_DGPU, &result);
- if (err) {
- if (err == -ENODEV)
- return 0;
- return err;
- }
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
- if (result & ASUS_WMI_DSTS_PRESENCE_BIT) {
- asus->dgpu_disable_available = true;
- asus->dgpu_disable = result & ASUS_WMI_DSTS_STATUS_BIT;
- }
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
+ if (result < 0)
+ return result;
- return 0;
+ return sysfs_emit(buf, "%d\n", result);
}
-static int dgpu_disable_write(struct asus_wmi *asus)
+/*
+ * A user may be required to store the value twice, typcial store first, then
+ * rescan PCI bus to activate power, then store a second time to save correctly.
+ * The reason for this is that an extra code path in the ACPI is enabled when
+ * the device and bus are powered.
+ */
+static ssize_t dgpu_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- u32 retval;
- u8 value;
- int err;
+ int result, err;
+ u32 disable;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
- /* Don't rely on type conversion */
- value = asus->dgpu_disable ? 1 : 0;
+ result = kstrtou32(buf, 10, &disable);
+ if (result)
+ return result;
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, value, &retval);
+ if (disable > 1)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result);
if (err) {
pr_warn("Failed to set dgpu disable: %d\n", err);
return err;
}
- if (retval > 1) {
- pr_warn("Failed to set dgpu disable (retval): 0x%x\n", retval);
+ if (result > 1) {
+ pr_warn("Failed to set dgpu disable (result): 0x%x\n", result);
return -EIO;
}
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "dgpu_disable");
- return 0;
+ return count;
}
+static DEVICE_ATTR_RW(dgpu_disable);
-static ssize_t dgpu_disable_show(struct device *dev,
+/* eGPU ********************************************************************/
+static ssize_t egpu_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
- u8 mode = asus->dgpu_disable;
+ int result;
- return sysfs_emit(buf, "%d\n", mode);
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
}
-/*
- * A user may be required to store the value twice, typcial store first, then
- * rescan PCI bus to activate power, then store a second time to save correctly.
- * The reason for this is that an extra code path in the ACPI is enabled when
- * the device and bus are powered.
- */
-static ssize_t dgpu_disable_store(struct device *dev,
+/* The ACPI call to enable the eGPU also disables the internal dGPU */
+static ssize_t egpu_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- bool disable;
- int result;
+ int result, err;
+ u32 enable;
struct asus_wmi *asus = dev_get_drvdata(dev);
- result = kstrtobool(buf, &disable);
- if (result)
- return result;
+ err = kstrtou32(buf, 10, &enable);
+ if (err)
+ return err;
- asus->dgpu_disable = disable;
+ if (enable > 1)
+ return -EINVAL;
- result = dgpu_disable_write(asus);
- if (result)
- return result;
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
+ if (err) {
+ pr_warn("Failed to set egpu disable: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set egpu disable (retval): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "egpu_enable");
return count;
}
+static DEVICE_ATTR_RW(egpu_enable);
-static DEVICE_ATTR_RW(dgpu_disable);
+/* gpu mux switch *************************************************************/
+static ssize_t gpu_mux_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
-/* eGPU ********************************************************************/
-static int egpu_enable_check_present(struct asus_wmi *asus)
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
+}
+
+static ssize_t gpu_mux_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- u32 result;
- int err;
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result, err;
+ u32 optimus;
+
+ err = kstrtou32(buf, 10, &optimus);
+ if (err)
+ return err;
- asus->egpu_enable_available = false;
+ if (optimus > 1)
+ return -EINVAL;
- err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_EGPU, &result);
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
if (err) {
- if (err == -ENODEV)
- return 0;
+ dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
return err;
}
-
- if (result & ASUS_WMI_DSTS_PRESENCE_BIT) {
- asus->egpu_enable_available = true;
- asus->egpu_enable = result & ASUS_WMI_DSTS_STATUS_BIT;
+ /* !1 is considered a fail by ASUS */
+ if (result != 1) {
+ dev_warn(dev, "Failed to set GPU MUX mode (result): 0x%x\n", result);
+ return -EIO;
}
- return 0;
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "gpu_mux_mode");
+
+ return count;
}
+static DEVICE_ATTR_RW(gpu_mux_mode);
-static int egpu_enable_write(struct asus_wmi *asus)
+/* TUF Laptop Keyboard RGB Modes **********************************************/
+static ssize_t kbd_rgb_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- u32 retval;
- u8 value;
+ u32 cmd, mode, r, g, b, speed;
int err;
- /* Don't rely on type conversion */
- value = asus->egpu_enable ? 1 : 0;
+ if (sscanf(buf, "%d %d %d %d %d %d", &cmd, &mode, &r, &g, &b, &speed) != 6)
+ return -EINVAL;
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, value, &retval);
+ cmd = !!cmd;
- if (err) {
- pr_warn("Failed to set egpu disable: %d\n", err);
- return err;
- }
+ /* These are the known usable modes across all TUF/ROG */
+ if (mode >= 12 || mode == 9)
+ mode = 10;
- if (retval > 1) {
- pr_warn("Failed to set egpu disable (retval): 0x%x\n", retval);
- return -EIO;
+ switch (speed) {
+ case 0:
+ speed = 0xe1;
+ break;
+ case 1:
+ speed = 0xeb;
+ break;
+ case 2:
+ speed = 0xf5;
+ break;
+ default:
+ speed = 0xeb;
}
- sysfs_notify(&asus->platform_device->dev.kobj, NULL, "egpu_enable");
+ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, ASUS_WMI_DEVID_TUF_RGB_MODE,
+ cmd | (mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL);
+ if (err)
+ return err;
- return 0;
+ return count;
}
+static DEVICE_ATTR_WO(kbd_rgb_mode);
-static ssize_t egpu_enable_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t kbd_rgb_mode_index_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
{
- struct asus_wmi *asus = dev_get_drvdata(dev);
- bool mode = asus->egpu_enable;
-
- return sysfs_emit(buf, "%d\n", mode);
+ return sysfs_emit(buf, "%s\n", "cmd mode red green blue speed");
}
+static DEVICE_ATTR_RO(kbd_rgb_mode_index);
-/* The ACPI call to enable the eGPU also disables the internal dGPU */
-static ssize_t egpu_enable_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- bool enable;
- int result;
-
- struct asus_wmi *asus = dev_get_drvdata(dev);
+static struct attribute *kbd_rgb_mode_attrs[] = {
+ &dev_attr_kbd_rgb_mode.attr,
+ &dev_attr_kbd_rgb_mode_index.attr,
+ NULL,
+};
- result = kstrtobool(buf, &enable);
- if (result)
- return result;
+static const struct attribute_group kbd_rgb_mode_group = {
+ .attrs = kbd_rgb_mode_attrs,
+};
- asus->egpu_enable = enable;
+/* TUF Laptop Keyboard RGB State **********************************************/
+static ssize_t kbd_rgb_state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 flags, cmd, boot, awake, sleep, keyboard;
+ int err;
- result = egpu_enable_write(asus);
- if (result)
- return result;
+ if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5)
+ return -EINVAL;
- /* Ensure that the kernel status of dgpu is updated */
- result = dgpu_disable_check_present(asus);
- if (result)
- return result;
+ if (cmd)
+ cmd = BIT(2);
+
+ flags = 0;
+ if (boot)
+ flags |= BIT(1);
+ if (awake)
+ flags |= BIT(3);
+ if (sleep)
+ flags |= BIT(5);
+ if (keyboard)
+ flags |= BIT(7);
+
+ /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */
+ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS,
+ ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
+ if (err)
+ return err;
return count;
}
+static DEVICE_ATTR_WO(kbd_rgb_state);
-static DEVICE_ATTR_RW(egpu_enable);
+static ssize_t kbd_rgb_state_index_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", "cmd boot awake sleep keyboard");
+}
+static DEVICE_ATTR_RO(kbd_rgb_state_index);
+
+static struct attribute *kbd_rgb_state_attrs[] = {
+ &dev_attr_kbd_rgb_state.attr,
+ &dev_attr_kbd_rgb_state_index.attr,
+ NULL,
+};
+
+static const struct attribute_group kbd_rgb_state_group = {
+ .attrs = kbd_rgb_state_attrs,
+};
+
+const struct attribute_group *kbd_rgb_mode_groups[] = {
+ NULL,
+ NULL,
+ NULL,
+};
/* Battery ********************************************************************/
@@ -771,7 +877,7 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
- return sprintf(buf, "%d\n", charge_end_threshold);
+ return sysfs_emit(buf, "%d\n", charge_end_threshold);
}
static DEVICE_ATTR_RW(charge_control_end_threshold);
@@ -1053,7 +1159,12 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
static int asus_wmi_led_init(struct asus_wmi *asus)
{
- int rv = 0, led_val;
+ int rv = 0, num_rgb_groups = 0, led_val;
+
+ if (asus->kbd_rgb_mode_available)
+ kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
+ if (asus->kbd_rgb_state_available)
+ kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_state_group;
asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
if (!asus->led_workqueue)
@@ -1081,6 +1192,9 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
asus->kbd_led.brightness_get = kbd_led_get;
asus->kbd_led.max_brightness = 3;
+ if (num_rgb_groups != 0)
+ asus->kbd_led.groups = kbd_rgb_mode_groups;
+
rv = led_classdev_register(&asus->platform_device->dev,
&asus->kbd_led);
if (rv)
@@ -1118,7 +1232,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
}
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MICMUTE_LED)) {
- asus->micmute_led.name = "asus::micmute";
+ asus->micmute_led.name = "platform::micmute";
asus->micmute_led.max_brightness = 1;
asus->micmute_led.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
asus->micmute_led.brightness_set_blocking = micmute_led_set;
@@ -1555,84 +1669,51 @@ exit:
}
/* Panel Overdrive ************************************************************/
-static int panel_od_check_present(struct asus_wmi *asus)
-{
- u32 result;
- int err;
-
- asus->panel_overdrive_available = false;
-
- err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_PANEL_OD, &result);
- if (err) {
- if (err == -ENODEV)
- return 0;
- return err;
- }
-
- if (result & ASUS_WMI_DSTS_PRESENCE_BIT) {
- asus->panel_overdrive_available = true;
- asus->panel_overdrive = result & ASUS_WMI_DSTS_STATUS_BIT;
- }
-
- return 0;
-}
-
-static int panel_od_write(struct asus_wmi *asus)
-{
- u32 retval;
- u8 value;
- int err;
-
- /* Don't rely on type conversion */
- value = asus->panel_overdrive ? 1 : 0;
-
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PANEL_OD, value, &retval);
-
- if (err) {
- pr_warn("Failed to set panel overdrive: %d\n", err);
- return err;
- }
-
- if (retval > 1) {
- pr_warn("Failed to set panel overdrive (retval): 0x%x\n", retval);
- return -EIO;
- }
-
- sysfs_notify(&asus->platform_device->dev.kobj, NULL, "panel_od");
-
- return 0;
-}
-
static ssize_t panel_od_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
- return sysfs_emit(buf, "%d\n", asus->panel_overdrive);
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_PANEL_OD);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
}
static ssize_t panel_od_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- bool overdrive;
- int result;
+ int result, err;
+ u32 overdrive;
struct asus_wmi *asus = dev_get_drvdata(dev);
- result = kstrtobool(buf, &overdrive);
+ result = kstrtou32(buf, 10, &overdrive);
if (result)
return result;
- asus->panel_overdrive = overdrive;
- result = panel_od_write(asus);
+ if (overdrive > 1)
+ return -EINVAL;
- if (result)
- return result;
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PANEL_OD, overdrive, &result);
+
+ if (err) {
+ pr_warn("Failed to set panel overdrive: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set panel overdrive (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "panel_od");
return count;
}
-
static DEVICE_ATTR_RW(panel_od);
/* Quirks *********************************************************************/
@@ -1782,6 +1863,18 @@ static int asus_fan_set_auto(struct asus_wmi *asus)
return -ENXIO;
}
+ /*
+ * Modern models like the G713 also have GPU fan control (this is not AGFN)
+ */
+ if (asus->gpu_fan_type == FAN_TYPE_SPEC83) {
+ status = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_FAN_CTRL,
+ 0, &retval);
+ if (status)
+ return status;
+
+ if (retval != 1)
+ return -EIO;
+ }
return 0;
}
@@ -1819,7 +1912,7 @@ static ssize_t pwm1_show(struct device *dev,
value = -1;
}
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t pwm1_store(struct device *dev,
@@ -1879,7 +1972,7 @@ static ssize_t fan1_input_show(struct device *dev,
return -ENXIO;
}
- return sprintf(buf, "%d\n", value < 0 ? -1 : value*100);
+ return sysfs_emit(buf, "%d\n", value < 0 ? -1 : value * 100);
}
static ssize_t pwm1_enable_show(struct device *dev,
@@ -1897,7 +1990,7 @@ static ssize_t pwm1_enable_show(struct device *dev,
* in practice on X532FL at least (the bit is always 0) and there's
* also nothing in the DSDT to indicate that this behaviour exists.
*/
- return sprintf(buf, "%d\n", asus->fan_pwm_mode);
+ return sysfs_emit(buf, "%d\n", asus->fan_pwm_mode);
}
static ssize_t pwm1_enable_store(struct device *dev,
@@ -1965,7 +2058,7 @@ static ssize_t fan1_label_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- return sprintf(buf, "%s\n", ASUS_FAN_DESC);
+ return sysfs_emit(buf, "%s\n", ASUS_FAN_DESC);
}
static ssize_t asus_hwmon_temp1(struct device *dev,
@@ -1984,9 +2077,57 @@ static ssize_t asus_hwmon_temp1(struct device *dev,
deci_kelvin_to_millicelsius(value & 0xFFFF));
}
+/* GPU fan on modern ROG laptops */
+static ssize_t pwm2_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", asus->gpu_fan_pwm_mode);
+}
+
+static ssize_t pwm2_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int state;
+ int value;
+ int ret;
+ u32 retval;
+
+ ret = kstrtouint(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ switch (state) { /* standard documented hwmon values */
+ case ASUS_FAN_CTRL_FULLSPEED:
+ value = 1;
+ break;
+ case ASUS_FAN_CTRL_AUTO:
+ value = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_FAN_CTRL,
+ value, &retval);
+ if (ret)
+ return ret;
+
+ if (retval != 1)
+ return -EIO;
+
+ asus->gpu_fan_pwm_mode = state;
+ return count;
+}
+
/* Fan1 */
static DEVICE_ATTR_RW(pwm1);
static DEVICE_ATTR_RW(pwm1_enable);
+static DEVICE_ATTR_RW(pwm2_enable);
static DEVICE_ATTR_RO(fan1_input);
static DEVICE_ATTR_RO(fan1_label);
@@ -1996,6 +2137,7 @@ static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
static struct attribute *hwmon_attributes[] = {
&dev_attr_pwm1.attr,
&dev_attr_pwm1_enable.attr,
+ &dev_attr_pwm2_enable.attr,
&dev_attr_fan1_input.attr,
&dev_attr_fan1_label.attr,
@@ -2006,7 +2148,7 @@ static struct attribute *hwmon_attributes[] = {
static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
struct attribute *attr, int idx)
{
- struct device *dev = container_of(kobj, struct device, kobj);
+ struct device *dev = kobj_to_dev(kobj);
struct asus_wmi *asus = dev_get_drvdata(dev->parent);
u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
@@ -2018,6 +2160,9 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|| attr == &dev_attr_pwm1_enable.attr) {
if (asus->fan_type == FAN_TYPE_NONE)
return 0;
+ } else if (attr == &dev_attr_pwm2_enable.attr) {
+ if (asus->gpu_fan_type == FAN_TYPE_NONE)
+ return 0;
} else if (attr == &dev_attr_temp1_input.attr) {
int err = asus_wmi_get_devstate(asus,
ASUS_WMI_DEVID_THERMAL_CTRL,
@@ -2060,6 +2205,7 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
static int asus_wmi_fan_init(struct asus_wmi *asus)
{
+ asus->gpu_fan_type = FAN_TYPE_NONE;
asus->fan_type = FAN_TYPE_NONE;
asus->agfn_pwm = -1;
@@ -2068,6 +2214,10 @@ static int asus_wmi_fan_init(struct asus_wmi *asus)
else if (asus_wmi_has_agfn_fan(asus))
asus->fan_type = FAN_TYPE_AGFN;
+ /* Modern models like G713 also have GPU fan control */
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL))
+ asus->gpu_fan_type = FAN_TYPE_SPEC83;
+
if (asus->fan_type == FAN_TYPE_NONE)
return -ENODEV;
@@ -2158,7 +2308,7 @@ static ssize_t fan_boost_mode_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_boost_mode);
+ return sysfs_emit(buf, "%d\n", asus->fan_boost_mode);
}
static ssize_t fan_boost_mode_store(struct device *dev,
@@ -2233,8 +2383,10 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
curves = &asus->custom_fan_curves[fan_idx];
err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf,
FAN_CURVE_BUF_LEN);
- if (err)
+ if (err) {
+ pr_warn("%s (0x%08x) failed: %d\n", __func__, fan_dev, err);
return err;
+ }
fan_curve_copy_from_buf(curves, buf);
curves->device_id = fan_dev;
@@ -2252,9 +2404,6 @@ static int fan_curve_check_present(struct asus_wmi *asus, bool *available,
err = fan_curve_get_factory_default(asus, fan_dev);
if (err) {
- pr_debug("fan_curve_get_factory_default(0x%08x) failed: %d\n",
- fan_dev, err);
- /* Don't cause probe to fail on devices without fan-curves */
return 0;
}
@@ -2711,7 +2860,7 @@ static ssize_t throttle_thermal_policy_show(struct device *dev,
struct asus_wmi *asus = dev_get_drvdata(dev);
u8 mode = asus->throttle_thermal_policy_mode;
- return scnprintf(buf, PAGE_SIZE, "%d\n", mode);
+ return sysfs_emit(buf, "%d\n", mode);
}
static ssize_t throttle_thermal_policy_store(struct device *dev,
@@ -3063,9 +3212,7 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
{
unsigned int key_value = 1;
bool autorelease = 1;
- int result, orig_code;
-
- orig_code = code;
+ int orig_code = code;
if (asus->driver->key_filter) {
asus->driver->key_filter(asus->driver, &code, &key_value,
@@ -3108,30 +3255,18 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
- if (asus->driver->quirks->use_kbd_dock_devid && code == NOTIFY_KBD_DOCK_CHANGE) {
- result = asus_wmi_get_devstate_simple(asus,
- ASUS_WMI_DEVID_KBD_DOCK);
- if (result >= 0) {
- input_report_switch(asus->inputdev, SW_TABLET_MODE,
- !result);
- input_sync(asus->inputdev);
- }
+ if (code == asus->tablet_switch_event_code) {
+ asus_wmi_tablet_mode_get_state(asus);
return;
}
- if (asus->driver->quirks->use_lid_flip_devid && code == NOTIFY_LID_FLIP) {
- lid_flip_tablet_mode_get_state(asus);
+ if (code == NOTIFY_KBD_FBM || code == NOTIFY_KBD_TTP) {
+ if (asus->fan_boost_mode_available)
+ fan_boost_mode_switch_next(asus);
+ if (asus->throttle_thermal_policy_available)
+ throttle_thermal_policy_switch_next(asus);
return;
- }
- if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) {
- fan_boost_mode_switch_next(asus);
- return;
- }
-
- if (asus->throttle_thermal_policy_available && code == NOTIFY_KBD_TTP) {
- throttle_thermal_policy_switch_next(asus);
- return;
}
if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle)
@@ -3283,6 +3418,7 @@ static struct attribute *platform_attributes[] = {
&dev_attr_touchpad.attr,
&dev_attr_egpu_enable.attr,
&dev_attr_dgpu_disable.attr,
+ &dev_attr_gpu_mux_mode.attr,
&dev_attr_lid_resume.attr,
&dev_attr_als_enable.attr,
&dev_attr_fan_boost_mode.attr,
@@ -3294,7 +3430,7 @@ static struct attribute *platform_attributes[] = {
static umode_t asus_sysfs_is_visible(struct kobject *kobj,
struct attribute *attr, int idx)
{
- struct device *dev = container_of(kobj, struct device, kobj);
+ struct device *dev = kobj_to_dev(kobj);
struct asus_wmi *asus = dev_get_drvdata(dev);
bool ok = true;
int devid = -1;
@@ -3313,6 +3449,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
ok = asus->egpu_enable_available;
else if (attr == &dev_attr_dgpu_disable.attr)
ok = asus->dgpu_disable_available;
+ else if (attr == &dev_attr_gpu_mux_mode.attr)
+ ok = asus->gpu_mux_mode_available;
else if (attr == &dev_attr_fan_boost_mode.attr)
ok = asus->fan_boost_mode_available;
else if (attr == &dev_attr_throttle_thermal_policy.attr)
@@ -3573,13 +3711,12 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform;
- err = egpu_enable_check_present(asus);
- if (err)
- goto fail_egpu_enable;
-
- err = dgpu_disable_check_present(asus);
- if (err)
- goto fail_dgpu_disable;
+ asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
+ asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
+ asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
+ asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
+ asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
+ asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
err = fan_boost_mode_check_present(asus);
if (err)
@@ -3595,10 +3732,6 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform_profile_setup;
- err = panel_od_check_present(asus);
- if (err)
- goto fail_panel_od;
-
err = asus_wmi_sysfs_init(asus->platform_device);
if (err)
goto fail_sysfs;
@@ -3681,10 +3814,7 @@ fail_platform_profile_setup:
if (asus->platform_profile_support)
platform_profile_remove();
fail_fan_boost_mode:
-fail_egpu_enable:
-fail_dgpu_disable:
fail_platform:
-fail_panel_od:
kfree(asus);
return err;
}
@@ -3743,9 +3873,7 @@ static int asus_hotk_resume(struct device *device)
if (asus_wmi_has_fnlock_key(asus))
asus_wmi_fnlock_update(asus);
- if (asus->driver->quirks->use_lid_flip_devid)
- lid_flip_tablet_mode_get_state(asus);
-
+ asus_wmi_tablet_mode_get_state(asus);
return 0;
}
@@ -3785,9 +3913,7 @@ static int asus_hotk_restore(struct device *device)
if (asus_wmi_has_fnlock_key(asus))
asus_wmi_fnlock_update(asus);
- if (asus->driver->quirks->use_lid_flip_devid)
- lid_flip_tablet_mode_get_state(asus);
-
+ asus_wmi_tablet_mode_get_state(asus);
return 0;
}
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
index f30252efe1db..65316998b898 100644
--- a/drivers/platform/x86/asus-wmi.h
+++ b/drivers/platform/x86/asus-wmi.h
@@ -25,14 +25,20 @@ struct module;
struct key_entry;
struct asus_wmi;
+enum asus_wmi_tablet_switch_mode {
+ asus_wmi_no_tablet_switch,
+ asus_wmi_kbd_dock_devid,
+ asus_wmi_lid_flip_devid,
+ asus_wmi_lid_flip_rog_devid,
+};
+
struct quirk_entry {
bool hotplug_wireless;
bool scalar_panel_brightness;
bool store_backlight_power;
bool wmi_backlight_set_devstate;
bool wmi_force_als_set;
- bool use_kbd_dock_devid;
- bool use_lid_flip_devid;
+ enum asus_wmi_tablet_switch_mode tablet_switch_mode;
int wapf;
/*
* For machines with AMD graphic chips, it will send out WMI event
diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c
index e07d3ba85a3f..0a259a27459f 100644
--- a/drivers/platform/x86/dell/dell-wmi-base.c
+++ b/drivers/platform/x86/dell/dell-wmi-base.c
@@ -344,6 +344,9 @@ static const struct key_entry dell_wmi_keymap_type_0011[] = {
* They are events with extended data
*/
static const struct key_entry dell_wmi_keymap_type_0012[] = {
+ /* Backlight brightness change event */
+ { KE_IGNORE, 0x0003, { KEY_RESERVED } },
+
/* Ultra-performance mode switch request */
{ KE_IGNORE, 0x000d, { KEY_RESERVED } },
diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c
index 074b7e68c227..c82b3d6867c5 100644
--- a/drivers/platform/x86/dell/dell-wmi-privacy.c
+++ b/drivers/platform/x86/dell/dell-wmi-privacy.c
@@ -174,15 +174,12 @@ static ssize_t dell_privacy_current_state_show(struct device *dev,
static DEVICE_ATTR_RO(dell_privacy_supported_type);
static DEVICE_ATTR_RO(dell_privacy_current_state);
-static struct attribute *privacy_attributes[] = {
+static struct attribute *privacy_attrs[] = {
&dev_attr_dell_privacy_supported_type.attr,
&dev_attr_dell_privacy_current_state.attr,
NULL,
};
-
-static const struct attribute_group privacy_attribute_group = {
- .attrs = privacy_attributes
-};
+ATTRIBUTE_GROUPS(privacy);
/*
* Describes the Device State class exposed by BIOS which can be consumed by
@@ -342,10 +339,6 @@ static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context)
if (ret)
return ret;
- ret = devm_device_add_group(&wdev->dev, &privacy_attribute_group);
- if (ret)
- return ret;
-
if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) {
ret = dell_privacy_leds_setup(&priv->wdev->dev);
if (ret)
@@ -374,6 +367,7 @@ static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = {
static struct wmi_driver dell_privacy_wmi_driver = {
.driver = {
.name = "dell-privacy",
+ .dev_groups = privacy_groups,
},
.probe = dell_privacy_wmi_probe,
.remove = dell_privacy_wmi_remove,
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
index 636bdfa83284..0a6411a8a104 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
@@ -270,7 +270,7 @@ void strlcpy_attr(char *dest, char *src)
size_t len = strlen(src) + 1;
if (len > 1 && len <= MAX_BUFF)
- strlcpy(dest, src, len);
+ strscpy(dest, src, len);
/*len can be zero because any property not-applicable to attribute can
* be empty so check only for too long buffers and log error
diff --git a/drivers/platform/x86/intel/chtwc_int33fe.c b/drivers/platform/x86/intel/chtwc_int33fe.c
index c52ac23e2331..2c9a7d52be07 100644
--- a/drivers/platform/x86/intel/chtwc_int33fe.c
+++ b/drivers/platform/x86/intel/chtwc_int33fe.c
@@ -219,7 +219,7 @@ static int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
/*
* Update node used in "usb-role-switch" property. Note that we
- * rely on software_node_register_nodes() to use the original
+ * rely on software_node_register_node_group() to use the original
* instance of properties instead of copying them.
*/
fusb302_mux_refs[0].node = mux_ref_node;
@@ -270,7 +270,7 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
}
memset(&board_info, 0, sizeof(board_info));
- strlcpy(board_info.type, "max17047", I2C_NAME_SIZE);
+ strscpy(board_info.type, "max17047", I2C_NAME_SIZE);
board_info.dev_name = "max17047";
board_info.fwnode = fwnode;
data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
@@ -361,7 +361,7 @@ static int cht_int33fe_typec_probe(struct platform_device *pdev)
}
memset(&board_info, 0, sizeof(board_info));
- strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
+ strscpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
board_info.dev_name = "fusb302";
board_info.fwnode = fwnode;
board_info.irq = fusb302_irq;
@@ -381,7 +381,7 @@ static int cht_int33fe_typec_probe(struct platform_device *pdev)
memset(&board_info, 0, sizeof(board_info));
board_info.dev_name = "pi3usb30532";
board_info.fwnode = fwnode;
- strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE);
+ strscpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE);
data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info);
if (IS_ERR(data->pi3usb30532)) {
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
index 24ffc8e2d2d1..1c29678e5727 100644
--- a/drivers/platform/x86/msi-laptop.c
+++ b/drivers/platform/x86/msi-laptop.c
@@ -53,8 +53,6 @@
#include <linux/input/sparse-keymap.h>
#include <acpi/video.h>
-#define MSI_DRIVER_VERSION "0.5"
-
#define MSI_LCD_LEVEL_MAX 9
#define MSI_EC_COMMAND_WIRELESS 0x10
@@ -592,6 +590,14 @@ static int dmi_check_cb(const struct dmi_system_id *dmi)
return 1;
}
+static unsigned long msi_work_delay(int msecs)
+{
+ if (quirks->ec_delay)
+ return msecs_to_jiffies(msecs);
+
+ return 0;
+}
+
static const struct dmi_system_id msi_dmi_table[] __initconst = {
{
.ident = "MSI S270",
@@ -705,6 +711,7 @@ static const struct dmi_system_id msi_dmi_table[] __initconst = {
},
{ }
};
+MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
static int rfkill_bluetooth_set(void *data, bool blocked)
{
@@ -785,7 +792,6 @@ static void msi_update_rfkill(struct work_struct *ignored)
msi_rfkill_set_state(rfk_threeg, !threeg_s);
}
static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill);
-static DECLARE_WORK(msi_rfkill_work, msi_update_rfkill);
static void msi_send_touchpad_key(struct work_struct *ignored)
{
@@ -801,7 +807,6 @@ static void msi_send_touchpad_key(struct work_struct *ignored)
KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true);
}
static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key);
-static DECLARE_WORK(msi_touchpad_work, msi_send_touchpad_key);
static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
struct serio *port)
@@ -819,20 +824,12 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
extended = false;
switch (data) {
case 0xE4:
- if (quirks->ec_delay) {
- schedule_delayed_work(&msi_touchpad_dwork,
- round_jiffies_relative(0.5 * HZ));
- } else
- schedule_work(&msi_touchpad_work);
+ schedule_delayed_work(&msi_touchpad_dwork, msi_work_delay(500));
break;
case 0x54:
case 0x62:
case 0x76:
- if (quirks->ec_delay) {
- schedule_delayed_work(&msi_rfkill_dwork,
- round_jiffies_relative(0.5 * HZ));
- } else
- schedule_work(&msi_rfkill_work);
+ schedule_delayed_work(&msi_rfkill_dwork, msi_work_delay(500));
break;
}
}
@@ -899,12 +896,7 @@ static int rfkill_init(struct platform_device *sdev)
}
/* schedule to run rfkill state initial */
- if (quirks->ec_delay) {
- schedule_delayed_work(&msi_rfkill_init,
- round_jiffies_relative(1 * HZ));
- } else
- schedule_work(&msi_rfkill_work);
-
+ schedule_delayed_work(&msi_rfkill_init, msi_work_delay(1000));
return 0;
err_threeg:
@@ -921,8 +913,7 @@ err_bluetooth:
return retval;
}
-#ifdef CONFIG_PM_SLEEP
-static int msi_laptop_resume(struct device *device)
+static int msi_scm_disable_hw_fn_handling(void)
{
u8 data;
int result;
@@ -942,6 +933,12 @@ static int msi_laptop_resume(struct device *device)
return 0;
}
+
+#ifdef CONFIG_PM_SLEEP
+static int msi_laptop_resume(struct device *device)
+{
+ return msi_scm_disable_hw_fn_handling();
+}
#endif
static int __init msi_laptop_input_setup(void)
@@ -974,7 +971,6 @@ err_free_dev:
static int __init load_scm_model_init(struct platform_device *sdev)
{
- u8 data;
int result;
if (!quirks->ec_read_only) {
@@ -988,12 +984,7 @@ static int __init load_scm_model_init(struct platform_device *sdev)
}
/* disable hardware control by fn key */
- result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
- if (result < 0)
- return result;
-
- result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
- data | MSI_STANDARD_EC_SCM_LOAD_MASK);
+ result = msi_scm_disable_hw_fn_handling();
if (result < 0)
return result;
@@ -1022,9 +1013,19 @@ fail_input:
rfkill_cleanup();
fail_rfkill:
-
return result;
+}
+
+static void msi_scm_model_exit(void)
+{
+ if (!quirks->load_scm_model)
+ return;
+ i8042_remove_filter(msi_laptop_i8042_filter);
+ cancel_delayed_work_sync(&msi_touchpad_dwork);
+ input_unregister_device(msi_laptop_input_dev);
+ cancel_delayed_work_sync(&msi_rfkill_dwork);
+ rfkill_cleanup();
}
static int __init msi_init(void)
@@ -1048,8 +1049,7 @@ static int __init msi_init(void)
return -EINVAL;
/* Register backlight stuff */
-
- if (quirks->old_ec_model ||
+ if (quirks->old_ec_model &&
acpi_video_get_backlight_type() == acpi_backlight_vendor) {
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
@@ -1108,19 +1108,12 @@ static int __init msi_init(void)
set_auto_brightness(auto_brightness);
}
- pr_info("driver " MSI_DRIVER_VERSION " successfully loaded\n");
-
return 0;
fail_create_attr:
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
fail_create_group:
- if (quirks->load_scm_model) {
- i8042_remove_filter(msi_laptop_i8042_filter);
- cancel_delayed_work_sync(&msi_rfkill_dwork);
- cancel_work_sync(&msi_rfkill_work);
- rfkill_cleanup();
- }
+ msi_scm_model_exit();
fail_scm_model_init:
platform_device_del(msipf_device);
fail_device_add:
@@ -1135,14 +1128,7 @@ fail_backlight:
static void __exit msi_cleanup(void)
{
- if (quirks->load_scm_model) {
- i8042_remove_filter(msi_laptop_i8042_filter);
- input_unregister_device(msi_laptop_input_dev);
- cancel_delayed_work_sync(&msi_rfkill_dwork);
- cancel_work_sync(&msi_rfkill_work);
- rfkill_cleanup();
- }
-
+ msi_scm_model_exit();
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
if (!quirks->old_ec_model && threeg_exists)
device_remove_file(&msipf_device->dev, &dev_attr_threeg);
@@ -1155,8 +1141,6 @@ static void __exit msi_cleanup(void)
if (auto_brightness != 2)
set_auto_brightness(1);
}
-
- pr_info("driver unloaded\n");
}
module_init(msi_init);
@@ -1164,16 +1148,4 @@ module_exit(msi_cleanup);
MODULE_AUTHOR("Lennart Poettering");
MODULE_DESCRIPTION("MSI Laptop Support");
-MODULE_VERSION(MSI_DRIVER_VERSION);
MODULE_LICENSE("GPL");
-
-MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
-MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
-MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
-MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
-MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
-MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
-MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
-MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
-MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnU270series:*");
-MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnU90/U100:*");
diff --git a/drivers/platform/x86/p2sb.c b/drivers/platform/x86/p2sb.c
index fb2e141f3eb8..384d0962ae93 100644
--- a/drivers/platform/x86/p2sb.c
+++ b/drivers/platform/x86/p2sb.c
@@ -42,10 +42,24 @@ static int p2sb_get_devfn(unsigned int *devfn)
return 0;
}
+/* Copy resource from the first BAR of the device in question */
static int p2sb_read_bar0(struct pci_dev *pdev, struct resource *mem)
{
- /* Copy resource from the first BAR of the device in question */
- *mem = pdev->resource[0];
+ struct resource *bar0 = &pdev->resource[0];
+
+ /* Make sure we have no dangling pointers in the output */
+ memset(mem, 0, sizeof(*mem));
+
+ /*
+ * We copy only selected fields from the original resource.
+ * Because a PCI device will be removed soon, we may not use
+ * any allocated data, hence we may not copy any pointers.
+ */
+ mem->start = bar0->start;
+ mem->end = bar0->end;
+ mem->flags = bar0->flags;
+ mem->desc = bar0->desc;
+
return 0;
}
diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c
index 154317e9910d..93a6414c6611 100644
--- a/drivers/platform/x86/pmc_atom.c
+++ b/drivers/platform/x86/pmc_atom.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Intel Atom SOC Power Management Controller Driver
- * Copyright (c) 2014, Intel Corporation.
+ * Intel Atom SoC Power Management Controller Driver
+ * Copyright (c) 2014-2015,2017,2022 Intel Corporation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -60,7 +60,7 @@ static const struct pmc_clk byt_clks[] = {
.freq = 19200000,
.parent_name = "xtal",
},
- {},
+ {}
};
static const struct pmc_clk cht_clks[] = {
@@ -69,7 +69,7 @@ static const struct pmc_clk cht_clks[] = {
.freq = 19200000,
.parent_name = NULL,
},
- {},
+ {}
};
static const struct pmc_bit_map d3_sts_0_map[] = {
@@ -105,7 +105,7 @@ static const struct pmc_bit_map d3_sts_0_map[] = {
{"LPSS2_F5_I2C5", BIT_LPSS2_F5_I2C5},
{"LPSS2_F6_I2C6", BIT_LPSS2_F6_I2C6},
{"LPSS2_F7_I2C7", BIT_LPSS2_F7_I2C7},
- {},
+ {}
};
static struct pmc_bit_map byt_d3_sts_1_map[] = {
@@ -113,21 +113,21 @@ static struct pmc_bit_map byt_d3_sts_1_map[] = {
{"OTG_SS_PHY", BIT_OTG_SS_PHY},
{"USH_SS_PHY", BIT_USH_SS_PHY},
{"DFX", BIT_DFX},
- {},
+ {}
};
static struct pmc_bit_map cht_d3_sts_1_map[] = {
{"SMB", BIT_SMB},
{"GMM", BIT_STS_GMM},
{"ISH", BIT_STS_ISH},
- {},
+ {}
};
static struct pmc_bit_map cht_func_dis_2_map[] = {
{"SMB", BIT_SMB},
{"GMM", BIT_FD_GMM},
{"ISH", BIT_FD_ISH},
- {},
+ {}
};
static const struct pmc_bit_map byt_pss_map[] = {
@@ -149,7 +149,7 @@ static const struct pmc_bit_map byt_pss_map[] = {
{"OTG_VCCA", PMC_PSS_BIT_OTG_VCCA},
{"USB", PMC_PSS_BIT_USB},
{"USB_SUS", PMC_PSS_BIT_USB_SUS},
- {},
+ {}
};
static const struct pmc_bit_map cht_pss_map[] = {
@@ -172,7 +172,7 @@ static const struct pmc_bit_map cht_pss_map[] = {
{"DFX_CLUSTER3", PMC_PSS_BIT_CHT_DFX_CLUSTER3},
{"DFX_CLUSTER4", PMC_PSS_BIT_CHT_DFX_CLUSTER4},
{"DFX_CLUSTER5", PMC_PSS_BIT_CHT_DFX_CLUSTER5},
- {},
+ {}
};
static const struct pmc_reg_map byt_reg_map = {
@@ -232,7 +232,7 @@ static void pmc_power_off(void)
pm1_cnt_port = acpi_base_addr + PM1_CNT;
pm1_cnt_value = inl(pm1_cnt_port);
- pm1_cnt_value &= SLEEP_TYPE_MASK;
+ pm1_cnt_value &= ~SLEEP_TYPE_MASK;
pm1_cnt_value |= SLEEP_TYPE_S5;
pm1_cnt_value |= SLEEP_ENABLE;
@@ -354,7 +354,7 @@ static bool pmc_clk_is_critical = true;
static int dmi_callback(const struct dmi_system_id *d)
{
- pr_info("%s critclks quirk enabled\n", d->ident);
+ pr_info("%s: PMC critical clocks quirk enabled\n", d->ident);
return 1;
}
@@ -417,8 +417,7 @@ static const struct dmi_system_id critclk_systems[] = {
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
},
},
-
- { /*sentinel*/ }
+ {}
};
static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap,
@@ -490,15 +489,11 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent)
return ret;
}
-/*
- * Data for PCI driver interface
- *
- * used by pci_match_id() call below.
- */
+/* Data for PCI driver interface used by pci_match_id() call below */
static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), (kernel_ulong_t)&byt_data },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), (kernel_ulong_t)&cht_data },
- { 0, },
+ {}
};
static int __init pmc_atom_init(void)
@@ -506,8 +501,9 @@ static int __init pmc_atom_init(void)
struct pci_dev *pdev = NULL;
const struct pci_device_id *ent;
- /* We look for our device - PCU PMC
- * we assume that there is max. one device.
+ /*
+ * We look for our device - PCU PMC.
+ * We assume that there is maximum one device.
*
* We can't use plain pci_driver mechanism,
* as the device is really a multiple function device,
@@ -519,7 +515,7 @@ static int __init pmc_atom_init(void)
if (ent)
return pmc_setup_dev(pdev, ent);
}
- /* Device not found. */
+ /* Device not found */
return -ENODEV;
}
@@ -527,6 +523,6 @@ device_initcall(pmc_atom_init);
/*
MODULE_AUTHOR("Aubrey Li <aubrey.li@linux.intel.com>");
-MODULE_DESCRIPTION("Intel Atom SOC Power Management Controller Interface");
+MODULE_DESCRIPTION("Intel Atom SoC Power Management Controller Interface");
MODULE_LICENSE("GPL v2");
*/
diff --git a/drivers/platform/x86/simatic-ipc.c b/drivers/platform/x86/simatic-ipc.c
index ca3647b751d5..ca76076fc706 100644
--- a/drivers/platform/x86/simatic-ipc.c
+++ b/drivers/platform/x86/simatic-ipc.c
@@ -41,10 +41,12 @@ static struct {
{SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E},
+ {SIMATIC_IPC_IPC227G, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
{SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E},
{SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E},
{SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E},
+ {SIMATIC_IPC_IPC427G, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
};
static int register_platform_devices(u32 station_id)
@@ -65,7 +67,8 @@ static int register_platform_devices(u32 station_id)
}
if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
- if (ledmode == SIMATIC_IPC_DEVICE_127E)
+ if (ledmode == SIMATIC_IPC_DEVICE_127E ||
+ ledmode == SIMATIC_IPC_DEVICE_227G)
pdevname = KBUILD_MODNAME "_leds_gpio";
platform_data.devmode = ledmode;
ipc_led_platform_device =
@@ -80,6 +83,11 @@ static int register_platform_devices(u32 station_id)
ipc_led_platform_device->name);
}
+ if (wdtmode == SIMATIC_IPC_DEVICE_227G) {
+ request_module("w83627hf_wdt");
+ return 0;
+ }
+
if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
platform_data.devmode = wdtmode;
ipc_wdt_platform_device =
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 22d4e8633e30..8fbe21ebcc52 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -7623,9 +7623,9 @@ static int __init volume_create_alsa_mixer(void)
data = card->private_data;
data->card = card;
- strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
+ strscpy(card->driver, TPACPI_ALSA_DRVNAME,
sizeof(card->driver));
- strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
+ strscpy(card->shortname, TPACPI_ALSA_SHRTNAME,
sizeof(card->shortname));
snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
(thinkpad_id.ec_version_str) ?
@@ -10592,10 +10592,9 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
/* Ensure initial values are correct */
dytc_profile_refresh();
- /* Set AMT correctly now we know current profile */
- if ((dytc_capabilities & BIT(DYTC_FC_PSC)) &&
- (dytc_capabilities & BIT(DYTC_FC_AMT)))
- dytc_control_amt(dytc_current_profile == PLATFORM_PROFILE_BALANCED);
+ /* Workaround for https://bugzilla.kernel.org/show_bug.cgi?id=216347 */
+ if (dytc_capabilities & BIT(DYTC_FC_PSC))
+ dytc_profile_set(NULL, PLATFORM_PROFILE_BALANCED);
return 0;
}
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index 030dc37d50b8..900ee68a4c0b 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -667,12 +667,15 @@ static void toshiba_eco_mode_available(struct toshiba_acpi_dev *dev)
return;
}
- if (out[0] == TOS_INPUT_DATA_ERROR) {
+ if (out[0] == TOS_INPUT_DATA_ERROR || out[0] == TOS_NOT_SUPPORTED) {
/*
* If we receive 0x8300 (Input Data Error), it means that the
* LED device is present, but that we just screwed the input
* parameters.
*
+ * On some laptops 0x8000 (Not supported) is also returned in
+ * this case, so we need to allow for that as well.
+ *
* Let's query the status of the LED to see if we really have a
* success response, indicating the actual presense of the LED,
* bail out otherwise.
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index aed293b5af81..aff23309b5d3 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -105,6 +105,12 @@ static const struct acpi_device_id wmi_device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
+/* allow duplicate GUIDs as these device drivers use struct wmi_driver */
+static const char * const allow_duplicates[] = {
+ "05901221-D566-11D1-B2F0-00A0C9062910", /* wmi-bmof */
+ NULL
+};
+
static struct platform_driver acpi_wmi_driver = {
.driver = {
.name = "acpi-wmi",
@@ -1073,6 +1079,23 @@ static const struct device_type wmi_type_data = {
.release = wmi_dev_release,
};
+/*
+ * _WDG is a static list that is only parsed at startup,
+ * so it's safe to count entries without extra protection.
+ */
+static int guid_count(const guid_t *guid)
+{
+ struct wmi_block *wblock;
+ int count = 0;
+
+ list_for_each_entry(wblock, &wmi_block_list, list) {
+ if (guid_equal(&wblock->gblock.guid, guid))
+ count++;
+ }
+
+ return count;
+}
+
static int wmi_create_device(struct device *wmi_bus_dev,
struct wmi_block *wblock,
struct acpi_device *device)
@@ -1080,6 +1103,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
struct acpi_device_info *info;
char method[WMI_ACPI_METHOD_NAME_SIZE];
int result;
+ uint count;
if (wblock->gblock.flags & ACPI_WMI_EVENT) {
wblock->dev.dev.type = &wmi_type_event;
@@ -1134,7 +1158,11 @@ static int wmi_create_device(struct device *wmi_bus_dev,
wblock->dev.dev.bus = &wmi_bus_type;
wblock->dev.dev.parent = wmi_bus_dev;
- dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
+ count = guid_count(&wblock->gblock.guid);
+ if (count)
+ dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
+ else
+ dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
device_initialize(&wblock->dev.dev);
@@ -1154,11 +1182,20 @@ static void wmi_free_devices(struct acpi_device *device)
}
}
-static bool guid_already_parsed(struct acpi_device *device, const guid_t *guid)
+static bool guid_already_parsed_for_legacy(struct acpi_device *device, const guid_t *guid)
{
struct wmi_block *wblock;
list_for_each_entry(wblock, &wmi_block_list, list) {
+ /* skip warning and register if we know the driver will use struct wmi_driver */
+ for (int i = 0; allow_duplicates[i] != NULL; i++) {
+ guid_t tmp;
+
+ if (guid_parse(allow_duplicates[i], &tmp))
+ continue;
+ if (guid_equal(&tmp, guid))
+ return false;
+ }
if (guid_equal(&wblock->gblock.guid, guid)) {
/*
* Because we historically didn't track the relationship
@@ -1208,13 +1245,7 @@ static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device)
if (debug_dump_wdg)
wmi_dump_wdg(&gblock[i]);
- /*
- * Some WMI devices, like those for nVidia hooks, have a
- * duplicate GUID. It's not clear what we should do in this
- * case yet, so for now, we'll just ignore the duplicate
- * for device creation.
- */
- if (guid_already_parsed(device, &gblock[i].guid))
+ if (guid_already_parsed_for_legacy(device, &gblock[i].guid))
continue;
wblock = kzalloc(sizeof(*wblock), GFP_KERNEL);
diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c
index 480375977435..4acd6fa8d43b 100644
--- a/drivers/platform/x86/x86-android-tablets.c
+++ b/drivers/platform/x86/x86-android-tablets.c
@@ -663,9 +663,23 @@ static const struct x86_i2c_client_info chuwi_hi8_i2c_clients[] __initconst = {
},
};
+static int __init chuwi_hi8_init(void)
+{
+ /*
+ * Avoid the acpi_unregister_gsi() call in x86_acpi_irq_helper_get()
+ * breaking the touchscreen + logging various errors when the Windows
+ * BIOS is used.
+ */
+ if (acpi_dev_present("MSSL0001", NULL, 1))
+ return -ENODEV;
+
+ return 0;
+}
+
static const struct x86_dev_info chuwi_hi8_info __initconst = {
.i2c_client_info = chuwi_hi8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(chuwi_hi8_i2c_clients),
+ .init = chuwi_hi8_init,
};
#define CZC_EC_EXTRA_PORT 0x68