summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/crypto/ccp/psp-dev.c11
-rw-r--r--drivers/crypto/ccp/sp-dev.c12
-rw-r--r--drivers/crypto/ccp/sp-dev.h3
-rw-r--r--drivers/crypto/ccp/sp-pci.c16
-rw-r--r--drivers/crypto/ccp/tee-dev.c56
-rw-r--r--drivers/crypto/ccp/tee-dev.h1
-rw-r--r--drivers/hid/hid-asus.c221
-rw-r--r--drivers/platform/mellanox/mlx-platform.c484
-rw-r--r--drivers/platform/surface/surface_aggregator_registry.c18
-rw-r--r--drivers/platform/surface/surfacepro3_button.c11
-rw-r--r--drivers/platform/wmi/Kconfig3
-rw-r--r--drivers/platform/wmi/Makefile5
-rw-r--r--drivers/platform/wmi/core.c160
-rw-r--r--drivers/platform/wmi/internal.h17
-rw-r--r--drivers/platform/wmi/marshalling.c247
-rw-r--r--drivers/platform/wmi/string.c92
-rw-r--r--drivers/platform/wmi/tests/Kconfig27
-rw-r--r--drivers/platform/wmi/tests/Makefile11
-rw-r--r--drivers/platform/wmi/tests/marshalling_kunit.c452
-rw-r--r--drivers/platform/wmi/tests/string_kunit.c296
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c40
-rw-r--r--drivers/platform/x86/amd/pmf/core.c161
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h33
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c33
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c14
-rw-r--r--drivers/platform/x86/amd/wbrf.c25
-rw-r--r--drivers/platform/x86/asus-wmi.c223
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c603
-rw-r--r--drivers/platform/x86/intel/pmc/core.c85
-rw-r--r--drivers/platform/x86/intel/pmc/core.h15
-rw-r--r--drivers/platform/x86/intel/pmt/class.c6
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c79
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c13
-rw-r--r--drivers/platform/x86/intel/wmi/sbl-fw-update.c43
-rw-r--r--drivers/platform/x86/intel/wmi/thunderbolt.c26
-rw-r--r--drivers/platform/x86/lenovo/Kconfig5
-rw-r--r--drivers/platform/x86/lenovo/Makefile2
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c39
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c206
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.c829
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.h65
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.c302
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.h25
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.c21
-rw-r--r--drivers/platform/x86/lenovo/wmi-other.c528
-rw-r--r--drivers/platform/x86/lenovo/yogabook.c12
-rw-r--r--drivers/platform/x86/uniwill/uniwill-acpi.c302
-rw-r--r--drivers/platform/x86/wmi-bmof.c34
-rw-r--r--drivers/platform/x86/xiaomi-wmi.c5
49 files changed, 5046 insertions, 871 deletions
diff --git a/drivers/crypto/ccp/psp-dev.c b/drivers/crypto/ccp/psp-dev.c
index 9e21da0e298a..5c7f7e02a7d8 100644
--- a/drivers/crypto/ccp/psp-dev.c
+++ b/drivers/crypto/ccp/psp-dev.c
@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)
return sp ? sp->psp_data : NULL;
}
+int psp_restore(struct sp_device *sp)
+{
+ struct psp_device *psp = sp->psp_data;
+ int ret = 0;
+
+ if (psp->tee_data)
+ ret = tee_restore(psp);
+
+ return ret;
+}
+
void psp_pci_init(void)
{
psp_master = psp_get_master_device();
diff --git a/drivers/crypto/ccp/sp-dev.c b/drivers/crypto/ccp/sp-dev.c
index 3467f6db4f50..f204aa5df96e 100644
--- a/drivers/crypto/ccp/sp-dev.c
+++ b/drivers/crypto/ccp/sp-dev.c
@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)
return 0;
}
+int sp_restore(struct sp_device *sp)
+{
+ if (sp->psp_data) {
+ int ret = psp_restore(sp);
+
+ if (ret)
+ return ret;
+ }
+
+ return sp_resume(sp);
+}
+
struct sp_device *sp_get_psp_master_device(void)
{
struct sp_device *i, *ret = NULL;
diff --git a/drivers/crypto/ccp/sp-dev.h b/drivers/crypto/ccp/sp-dev.h
index 1335a83fe052..a83751cfd006 100644
--- a/drivers/crypto/ccp/sp-dev.h
+++ b/drivers/crypto/ccp/sp-dev.h
@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);
int sp_suspend(struct sp_device *sp);
int sp_resume(struct sp_device *sp);
+int sp_restore(struct sp_device *sp);
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data);
void sp_free_ccp_irq(struct sp_device *sp, void *data);
@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);
void psp_pci_init(void);
void psp_dev_destroy(struct sp_device *sp);
void psp_pci_exit(void);
+int psp_restore(struct sp_device *sp);
#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
@@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }
static inline void psp_pci_init(void) { }
static inline void psp_dev_destroy(struct sp_device *sp) { }
static inline void psp_pci_exit(void) { }
+static inline int psp_restore(struct sp_device *sp) { return 0; }
#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
diff --git a/drivers/crypto/ccp/sp-pci.c b/drivers/crypto/ccp/sp-pci.c
index 8891ceee1d7d..6ac805d99ccb 100644
--- a/drivers/crypto/ccp/sp-pci.c
+++ b/drivers/crypto/ccp/sp-pci.c
@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)
return sp_resume(sp);
}
+static int __maybe_unused sp_pci_restore(struct device *dev)
+{
+ struct sp_device *sp = dev_get_drvdata(dev);
+
+ return sp_restore(sp);
+}
+
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
static const struct sev_vdata sevv1 = {
.cmdresp_reg = 0x10580, /* C2PMSG_32 */
@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = {
};
MODULE_DEVICE_TABLE(pci, sp_pci_table);
-static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume);
+static const struct dev_pm_ops sp_pci_pm_ops = {
+ .suspend = pm_sleep_ptr(sp_pci_suspend),
+ .resume = pm_sleep_ptr(sp_pci_resume),
+ .freeze = pm_sleep_ptr(sp_pci_suspend),
+ .thaw = pm_sleep_ptr(sp_pci_resume),
+ .poweroff = pm_sleep_ptr(sp_pci_suspend),
+ .restore_early = pm_sleep_ptr(sp_pci_restore),
+};
static struct pci_driver sp_pci_driver = {
.name = "ccp",
diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c
index 5e1d80724678..92ffa412622a 100644
--- a/drivers/crypto/ccp/tee-dev.c
+++ b/drivers/crypto/ccp/tee-dev.c
@@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)
kfree(cmd);
}
+static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
+ TEE_DEFAULT_CMD_TIMEOUT, &reg);
+ if (ret) {
+ dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
+ psp_dead = true;
+ return false;
+ }
+
+ if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
+ dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
+ FIELD_GET(PSP_CMDRESP_STS, reg));
+ psp_dead = true;
+ return false;
+ }
+
+ return true;
+}
+
static int tee_init_ring(struct psp_tee_device *tee)
{
int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
struct tee_init_ring_cmd *cmd;
+ bool retry = false;
unsigned int reg;
int ret;
@@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)
/* Send command buffer details to Trusted OS by writing to
* CPU-PSP message registers
*/
+retry_init:
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
@@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee)
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
+ /*
+ * During the hibernate resume sequence driver may have gotten loaded
+ * but the ring not properly destroyed. If the ring doesn't work, try
+ * to destroy and re-init once.
+ */
+ if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
+ dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
+ if (tee_send_destroy_cmd(tee)) {
+ retry = true;
+ goto retry_init;
+ }
+ }
dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
tee_free_ring(tee);
+ psp_dead = true;
ret = -EIO;
}
@@ -136,24 +174,13 @@ free_buf:
static void tee_destroy_ring(struct psp_tee_device *tee)
{
- unsigned int reg;
- int ret;
-
if (!tee->rb_mgr.ring_start)
return;
if (psp_dead)
goto free_ring;
- ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
- TEE_DEFAULT_CMD_TIMEOUT, &reg);
- if (ret) {
- dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
- psp_dead = true;
- } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
- dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
- FIELD_GET(PSP_CMDRESP_STS, reg));
- }
+ tee_send_destroy_cmd(tee);
free_ring:
tee_free_ring(tee);
@@ -365,3 +392,8 @@ int psp_check_tee_status(void)
return 0;
}
EXPORT_SYMBOL(psp_check_tee_status);
+
+int tee_restore(struct psp_device *psp)
+{
+ return tee_init_ring(psp->tee_data);
+}
diff --git a/drivers/crypto/ccp/tee-dev.h b/drivers/crypto/ccp/tee-dev.h
index ea9a2b7c05f5..c23416cb7bb3 100644
--- a/drivers/crypto/ccp/tee-dev.h
+++ b/drivers/crypto/ccp/tee-dev.h
@@ -111,5 +111,6 @@ struct tee_ring_cmd {
int tee_dev_init(struct psp_device *psp);
void tee_dev_destroy(struct psp_device *psp);
+int tee_restore(struct psp_device *psp);
#endif /* __TEE_DEV_H__ */
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index df7c03dde67f..8ffcd12038e8 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -28,7 +28,6 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
-#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
@@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_REPORT_ID 0x0d
#define INPUT_REPORT_ID 0x5d
#define FEATURE_KBD_REPORT_ID 0x5a
-#define FEATURE_KBD_REPORT_SIZE 16
+#define FEATURE_KBD_REPORT_SIZE 64
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
@@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_T90CHI BIT(9)
#define QUIRK_MEDION_E1239T BIT(10)
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
-#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_HID_FN_LOCK BIT(14)
+#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
- struct led_classdev cdev;
+ struct asus_hid_listener listener;
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
@@ -138,7 +138,6 @@ struct asus_drvdata {
struct input_dev *tp_kbd_input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
- bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
@@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
usage->hid & HID_USAGE);
}
- if (drvdata->quirks & QUIRK_HID_FN_LOCK &&
- usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
- drvdata->fn_lock = !drvdata->fn_lock;
- schedule_work(&drvdata->fn_lock_sync_work);
+ if (usage->type == EV_KEY && value) {
+ switch (usage->code) {
+ case KEY_KBDILLUMUP:
+ return !asus_hid_event(ASUS_EV_BRTUP);
+ case KEY_KBDILLUMDOWN:
+ return !asus_hid_event(ASUS_EV_BRTDOWN);
+ case KEY_KBDILLUMTOGGLE:
+ return !asus_hid_event(ASUS_EV_BRTTOGGLE);
+ case KEY_FN_ESC:
+ if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
+ drvdata->fn_lock = !drvdata->fn_lock;
+ schedule_work(&drvdata->fn_lock_sync_work);
+ }
+ break;
+ }
}
return 0;
@@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
+ /*
+ * The handshake is first sent as a set_report, then retrieved
+ * from a get_report. They should be equal.
+ */
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
- if (ret < 0)
- hid_err(hdev, "Asus failed to send init command: %d\n", ret);
+ if (ret < 0) {
+ hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
+ report_id, ret);
+ return ret;
+ }
- return ret;
+ u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
+ if (!readbuf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
+ FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
+ report_id, ret);
+ } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
+ hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
+ report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
+ }
+
+ /*
+ * Do not return error if handshake is wrong until this is
+ * verified to work for all devices.
+ */
+ return 0;
}
static int asus_kbd_get_functions(struct hid_device *hdev,
@@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
if (!readbuf)
return -ENOMEM;
- ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
+ ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
@@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
spin_unlock_irqrestore(&led->lock, flags);
}
-static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
+ int brightness)
{
- struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
- cdev);
+ struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
+ listener);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
@@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
asus_schedule_work(led);
}
-static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
-{
- struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
- cdev);
- enum led_brightness brightness;
- unsigned long flags;
-
- spin_lock_irqsave(&led->lock, flags);
- brightness = led->brightness;
- spin_unlock_irqrestore(&led->lock, flags);
-
- return brightness;
-}
-
static void asus_kbd_backlight_work(struct work_struct *work)
{
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
@@ -609,34 +631,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
}
-/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
- * precedence. We only activate HID-based backlight control when the
- * WMI control is not available.
- */
-static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
-{
- struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
- u32 value;
- int ret;
-
- if (!IS_ENABLED(CONFIG_ASUS_WMI))
- return false;
-
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
- dmi_check_system(asus_use_hid_led_dmi_ids)) {
- hid_info(hdev, "using HID for asus::kbd_backlight\n");
- return false;
- }
-
- ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
- ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
- hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
- if (ret)
- return false;
-
- return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
-}
-
/*
* We don't care about any other part of the string except the version section.
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
@@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
unsigned char kbd_func;
int ret;
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
- /* Initialize keyboard */
- ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
- if (ret < 0)
- return ret;
-
- /* The LED endpoint is initialised in two HID */
- ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
- if (ret < 0)
- return ret;
-
- ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
- if (ret < 0)
- return ret;
+ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
+ if (ret < 0)
+ return ret;
- if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
- ret = asus_kbd_disable_oobe(hdev);
- if (ret < 0)
- return ret;
- }
+ /* Get keyboard functions */
+ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+ if (ret < 0)
+ return ret;
- if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
- intf = to_usb_interface(hdev->dev.parent);
- udev = interface_to_usbdev(intf);
- validate_mcu_fw_version(hdev,
- le16_to_cpu(udev->descriptor.idProduct));
- }
+ /* Check for backlight support */
+ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
+ return -ENODEV;
- } else {
- /* Initialize keyboard */
- ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
- if (ret < 0)
- return ret;
+ if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
+ asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
+ }
- /* Get keyboard functions */
- ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
+ if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
+ ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
+ }
- /* Check for backlight support */
- if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
- return -ENODEV;
+ if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
+ intf = to_usb_interface(hdev->dev.parent);
+ udev = interface_to_usbdev(intf);
+ validate_mcu_fw_version(hdev,
+ le16_to_cpu(udev->descriptor.idProduct));
}
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
@@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->removed = false;
drvdata->kbd_backlight->brightness = 0;
drvdata->kbd_backlight->hdev = hdev;
- drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
- drvdata->kbd_backlight->cdev.max_brightness = 3;
- drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
- drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
+ drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
- ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
+ ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
if (ret < 0) {
/* No need to have this still around */
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
drvdata->input = input;
- if (drvdata->enable_backlight &&
- !asus_kbd_wmi_led_control_present(hdev) &&
- asus_kbd_register_leds(hdev))
- hid_warn(hdev, "Failed to initialize backlight.\n");
-
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = true;
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
@@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev,
return -1;
}
- /*
- * Check and enable backlight only on devices with UsagePage ==
- * 0xff31 to avoid initializing the keyboard firmware multiple
- * times on devices with multiple HID descriptors but same
- * PID/VID.
- */
- if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
- drvdata->enable_backlight = true;
-
set_bit(EV_REP, hi->input->evbit);
return 1;
}
@@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
if (drvdata->kbd_backlight) {
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
- drvdata->kbd_backlight->cdev.brightness };
+ drvdata->kbd_backlight->brightness };
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0) {
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
@@ -1229,8 +1193,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
- int ret;
+ struct hid_report_enum *rep_enum;
struct asus_drvdata *drvdata;
+ struct hid_report *rep;
+ bool is_vendor = false;
+ int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
@@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
+ /* Check for vendor for RGB init and handle generic devices properly. */
+ rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
+ is_vendor = true;
+ }
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "Asus hw start failed: %d\n", ret);
return ret;
}
+ if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
+ asus_kbd_register_leds(hdev))
+ hid_warn(hdev, "Failed to initialize backlight.\n");
+
+ /*
+ * For ROG keyboards, skip rename for consistency and ->input check as
+ * some devices do not have inputs.
+ */
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
+ return 0;
+
/*
* Check that input registration succeeded. Checking that
* HID_CLAIMED_INPUT is set prevents a UAF when all input devices
@@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev)
unsigned long flags;
if (drvdata->kbd_backlight) {
+ asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
+
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
@@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c
index efd0c074ad93..efcba68d3aa5 100644
--- a/drivers/platform/mellanox/mlx-platform.c
+++ b/drivers/platform/mellanox/mlx-platform.c
@@ -6,6 +6,8 @@
* Copyright (C) 2016-2018 Vadim Pasternak <vadimp@mellanox.com>
*/
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
@@ -727,6 +729,16 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = {
},
};
+/* Platform hotplug dgx data */
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pdb_items_data[] = {
+ {
+ .label = "pdb1",
+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
+ .mask = BIT(0),
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ },
+};
+
static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = {
{
.label = "pwr1",
@@ -776,6 +788,15 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_ng800_items_data[] =
},
};
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_pwr_items_data[] = {
+ {
+ .label = "pwr1",
+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
+ .mask = BIT(0),
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ },
+};
+
static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = {
{
.label = "fan1",
@@ -1399,6 +1420,45 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ext_items[] = {
}
};
+static struct mlxreg_core_item mlxplat_mlxcpld_ext_dgx_items[] = {
+ {
+ .data = mlxplat_mlxcpld_dgx_pdb_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
+ .mask = MLXPLAT_CPLD_PSU_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pdb_items_data),
+ .inversed = 1,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_dgx_pwr_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
+ .mask = MLXPLAT_CPLD_PWR_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_dgx_pwr_items_data),
+ .inversed = 0,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_default_ng_fan_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
+ .mask = MLXPLAT_CPLD_FAN_NG_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data),
+ .inversed = 1,
+ .health = false,
+ },
+ {
+ .data = mlxplat_mlxcpld_default_asic_items_data,
+ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
+ .mask = MLXPLAT_CPLD_ASIC_MASK,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data),
+ .inversed = 0,
+ .health = true,
+ },
+};
+
static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = {
{
.data = mlxplat_mlxcpld_default_ng_psu_items_data,
@@ -1451,6 +1511,16 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = {
};
static
+struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_dgx_ext_data = {
+ .items = mlxplat_mlxcpld_ext_dgx_items,
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_dgx_items),
+ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
+ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX,
+ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET,
+ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2,
+};
+
+static
struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = {
.items = mlxplat_mlxcpld_ng800_items,
.count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items),
@@ -4625,6 +4695,359 @@ static struct mlxreg_core_platform_data mlxplat_default_ng_regs_io_data = {
.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_regs_io_data),
};
+/* Platform register access for next generation systems families data */
+static struct mlxreg_core_data mlxplat_mlxcpld_dgx_ng_regs_io_data[] = {
+ {
+ .label = "cpld1_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld2_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld3_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld4_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld1_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld2_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld3_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld4_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
+ .label = "cpld1_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld2_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld3_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "cpld4_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "asic_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0200,
+ },
+ {
+ .label = "reset_long_pb",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_short_pb",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(1),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_aux_pwr_or_ref",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_swb_dc_dc_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_from_asic",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_swb_wd",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_asic_thermal",
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(7),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_sw_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_platform",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_soc",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_wd",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_system",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(1),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_sw_pwr_off",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_comex_thermal",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_reload_bios",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "reset_pdb_pwr_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "pdb_reset_stby",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0200,
+ },
+ {
+ .label = "pwr_cycle",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0200,
+ },
+ {
+ .label = "pwr_down",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0200,
+ },
+ {
+ .label = "deep_pwr_cycle",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0200,
+ },
+ {
+ .label = "latch_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0200,
+ },
+ {
+ .label = "jtag_cap",
+ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
+ .mask = MLXPLAT_CPLD_FU_CAP_MASK,
+ .bit = 1,
+ .mode = 0444,
+ },
+ {
+ .label = "jtag_enable",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg1",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg2",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg3",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "dbg4",
+ .reg = MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0644,
+ },
+ {
+ .label = "asic_health",
+ .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET,
+ .mask = MLXPLAT_CPLD_ASIC_MASK,
+ .bit = 1,
+ .mode = 0444,
+ },
+ {
+ .label = "fan_dir",
+ .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_safe_mode",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_active_image",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_auth_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(6),
+ .mode = 0444,
+ },
+ {
+ .label = "bios_upgrade_fail",
+ .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(7),
+ .mode = 0444,
+ },
+ {
+ .label = "voltreg_update_status",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET,
+ .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK,
+ .bit = 5,
+ .mode = 0444,
+ },
+ {
+ .label = "pwr_converter_prog_en",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0644,
+ .secured = 1,
+ },
+ {
+ .label = "vpd_wp",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(3),
+ .mode = 0644,
+ },
+ {
+ .label = "pcie_asic_reset_dis",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(4),
+ .mode = 0644,
+ },
+ {
+ .label = "shutdown_unlock",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(5),
+ .mode = 0644,
+ },
+ {
+ .label = "config1",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "config2",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "config3",
+ .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
+ .label = "ufm_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+};
+
+static struct mlxreg_core_platform_data mlxplat_dgx_ng_regs_io_data = {
+ .data = mlxplat_mlxcpld_dgx_ng_regs_io_data,
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_dgx_ng_regs_io_data),
+};
+
/* Platform register access for modular systems families data */
static struct mlxreg_core_data mlxplat_mlxcpld_modular_regs_io_data[] = {
{
@@ -7239,6 +7662,32 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
return mlxplat_register_platform_device();
}
+static int __init mlxplat_dmi_ng400_dgx_matched(const struct dmi_system_id *dmi)
+{
+ int i;
+
+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_default_mux_data);
+ mlxplat_mux_data = mlxplat_default_mux_data;
+ for (i = 0; i < mlxplat_mux_num; i++) {
+ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels;
+ mlxplat_mux_data[i].n_values =
+ ARRAY_SIZE(mlxplat_msn21xx_channels);
+ }
+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
+ mlxplat_hotplug->deferred_nr =
+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
+ mlxplat_led = &mlxplat_default_ng_led_data;
+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
+ mlxplat_fan = &mlxplat_default_fan_data;
+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
+
+ return mlxplat_register_platform_device();
+}
+
static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
{
int i;
@@ -7323,6 +7772,27 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
return mlxplat_register_platform_device();
}
+static int __init mlxplat_dmi_ng800_dgx_matched(const struct dmi_system_id *dmi)
+{
+ int i;
+
+ mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM;
+ mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data);
+ mlxplat_mux_data = mlxplat_ng800_mux_data;
+ mlxplat_hotplug = &mlxplat_mlxcpld_dgx_ext_data;
+ mlxplat_hotplug->deferred_nr =
+ mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
+ mlxplat_led = &mlxplat_default_ng_led_data;
+ mlxplat_regs_io = &mlxplat_dgx_ng_regs_io_data;
+ mlxplat_fan = &mlxplat_default_fan_data;
+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
+ mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
+ mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
+
+ return mlxplat_register_platform_device();
+}
+
static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
{
int i;
@@ -7459,6 +7929,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
},
},
{
+ .callback = mlxplat_dmi_ng400_dgx_matched,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI173"),
+ },
+ },
+ {
.callback = mlxplat_dmi_ng400_matched,
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "VMOD0010"),
@@ -7471,6 +7948,13 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
},
},
{
+ .callback = mlxplat_dmi_ng800_dgx_matched,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI174"),
+ },
+ },
+ {
.callback = mlxplat_dmi_ng800_matched,
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "VMOD0013"),
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index 78ac3a8fbb73..0599d5adf02e 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = {
NULL,
};
+/* Devices for Surface Pro 11 (ARM/QCOM) */
+static const struct software_node *ssam_node_group_sp11[] = {
+ &ssam_node_root,
+ &ssam_node_hub_kip,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_tmp_sensors,
+ &ssam_node_hid_kip_keyboard,
+ &ssam_node_hid_kip_penstash,
+ &ssam_node_hid_kip_touchpad,
+ &ssam_node_hid_kip_fwupd,
+ &ssam_node_hid_sam_sensors,
+ &ssam_node_kip_tablet_switch,
+ NULL,
+};
+
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Pro 9 5G (ARM/QCOM) */
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
+ /* Surface Pro 11 (ARM/QCOM) */
+ { .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c
index 2755601f979c..e652c85c9161 100644
--- a/drivers/platform/surface/surfacepro3_button.c
+++ b/drivers/platform/surface/surfacepro3_button.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/acpi.h>
@@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device)
struct surface_button *button;
struct input_dev *input;
const char *hid = acpi_device_hid(device);
- char *name;
int error;
if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
@@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_button;
}
- name = acpi_device_name(device);
- strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
+ strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
- input->name = name;
+ input->name = acpi_device_name(device);
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &device->dev;
@@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_input;
device_init_wakeup(&device->dev, true);
- dev_info(&device->dev,
- "%s [%s]\n", name, acpi_device_bid(device));
+ dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device),
+ acpi_device_bid(device));
return 0;
err_free_input:
diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig
index 77fcbb18746b..d62f51ff3b7f 100644
--- a/drivers/platform/wmi/Kconfig
+++ b/drivers/platform/wmi/Kconfig
@@ -6,6 +6,7 @@
menuconfig ACPI_WMI
tristate "ACPI-WMI support"
depends on ACPI && X86
+ select NLS
help
This option enables support for the ACPI-WMI driver core.
@@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
userspace applications but will cause the registration of WMI devices with
the same GUID to fail in some corner cases.
+source "drivers/platform/wmi/tests/Kconfig"
+
endif # ACPI_WMI
diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile
index 98393d7391ec..2feff94a5594 100644
--- a/drivers/platform/wmi/Makefile
+++ b/drivers/platform/wmi/Makefile
@@ -4,5 +4,8 @@
# ACPI WMI core
#
-wmi-y := core.o
+wmi-y := core.o marshalling.o string.o
obj-$(CONFIG_ACPI_WMI) += wmi.o
+
+# Unit tests
+obj-y += tests/
diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c
index 6878c4fcb0b5..1601bf9fe135 100644
--- a/drivers/platform/wmi/core.c
+++ b/drivers/platform/wmi/core.c
@@ -23,6 +23,7 @@
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
@@ -33,6 +34,8 @@
#include <linux/wmi.h>
#include <linux/fs.h>
+#include "internal.h"
+
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
@@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method
EXPORT_SYMBOL_GPL(wmi_evaluate_method);
/**
- * wmidev_evaluate_method - Evaluate a WMI method
+ * wmidev_evaluate_method - Evaluate a WMI method (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @method_id: Method ID to call
@@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met
}
EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
+/**
+ * wmidev_invoke_method - Invoke a WMI method
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * @in: Mandatory WMI buffer containing input for the method call
+ * @out: Optional WMI buffer to return the method results
+ *
+ * Invoke a WMI method, the caller must free the resulting data inside @out.
+ * Said data is guaranteed to be aligned on a 8-byte boundary.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
+ const struct wmi_buffer *in, struct wmi_buffer *out)
+{
+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
+ struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer ain;
+ union acpi_object *obj;
+ acpi_status status;
+ int ret;
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
+ ret = wmi_marshal_string(in, &ain);
+ if (ret < 0)
+ return ret;
+ } else {
+ if (in->length > U32_MAX)
+ return -E2BIG;
+
+ ain.length = in->length;
+ ain.pointer = in->data;
+ }
+
+ if (out)
+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout);
+ else
+ status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL);
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING)
+ kfree(ain.pointer);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (!out)
+ return 0;
+
+ obj = aout.pointer;
+ if (!obj) {
+ out->length = 0;
+ out->data = ZERO_SIZE_PTR;
+
+ return 0;
+ }
+
+ ret = wmi_unmarshal_acpi_object(obj, out);
+ kfree(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmidev_invoke_method);
+
static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
struct acpi_buffer *out)
{
@@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
EXPORT_SYMBOL_GPL(wmi_query_block);
/**
- * wmidev_block_query - Return contents of a WMI block
+ * wmidev_block_query - Return contents of a WMI block (deprectated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
*
@@ -453,6 +520,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
EXPORT_SYMBOL_GPL(wmidev_block_query);
/**
+ * wmidev_query_block - Return contents of a WMI data block
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @out: WMI buffer to fill
+ *
+ * Query a WMI data block, the caller must free the resulting data inside @out.
+ * Said data is guaranteed to be aligned on a 8-byte boundary.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out)
+{
+ union acpi_object *obj;
+ int ret;
+
+ obj = wmidev_block_query(wdev, instance);
+ if (!obj)
+ return -EIO;
+
+ ret = wmi_unmarshal_acpi_object(obj, out);
+ kfree(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmidev_query_block);
+
+/**
* wmi_set_block - Write to a WMI block (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @instance: Instance index
@@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp
EXPORT_SYMBOL_GPL(wmi_set_block);
/**
- * wmidev_block_set - Write to a WMI block
+ * wmidev_block_set - Write to a WMI block (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @in: Buffer containing new values for the data block
@@ -536,6 +630,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct
EXPORT_SYMBOL_GPL(wmidev_block_set);
/**
+ * wmidev_set_block - Write to a WMI data block
+ * @wdev: A wmi bus device from a driver
+ * @instance: Instance index
+ * @in: WMI buffer containing new values for the data block
+ *
+ * Write the content of @in into a WMI data block.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in)
+{
+ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
+ struct acpi_buffer buffer;
+ acpi_status status;
+ int ret;
+
+ if (wblock->gblock.flags & ACPI_WMI_STRING) {
+ ret = wmi_marshal_string(in, &buffer);
+ if (ret < 0)
+ return ret;
+ } else {
+ if (in->length > U32_MAX)
+ return -E2BIG;
+
+ buffer.length = in->length;
+ buffer.pointer = in->data;
+ }
+
+ status = wmidev_block_set(wdev, instance, &buffer);
+ if (wblock->gblock.flags & ACPI_WMI_STRING)
+ kfree(buffer.pointer);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wmidev_set_block);
+
+/**
* wmi_install_notify_handler - Register handler for WMI events (deprecated)
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @handler: Function to handle notifications
@@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev)
return -ENODEV;
}
- if (wdriver->notify) {
+ if (wdriver->notify || wdriver->notify_new) {
if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
return -ENODEV;
}
@@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
{
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
+ struct wmi_buffer buffer;
+ int ret;
if (!obj && !driver->no_notify_data) {
dev_warn(&wblock->dev.dev, "Event contains no event data\n");
@@ -1229,6 +1365,22 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
if (driver->notify)
driver->notify(&wblock->dev, obj);
+
+ if (driver->notify_new) {
+ if (!obj) {
+ driver->notify_new(&wblock->dev, NULL);
+ return;
+ }
+
+ ret = wmi_unmarshal_acpi_object(obj, &buffer);
+ if (ret < 0) {
+ dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret);
+ return;
+ }
+
+ driver->notify_new(&wblock->dev, &buffer);
+ kfree(buffer.data);
+ }
}
static int wmi_notify_device(struct device *dev, void *data)
diff --git a/drivers/platform/wmi/internal.h b/drivers/platform/wmi/internal.h
new file mode 100644
index 000000000000..9a39ffa31ad1
--- /dev/null
+++ b/drivers/platform/wmi/internal.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Internal interfaces used by the WMI core.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#ifndef _WMI_INTERNAL_H_
+#define _WMI_INTERNAL_H_
+
+union acpi_object;
+struct wmi_buffer;
+
+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer);
+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
+
+#endif /* _WMI_INTERNAL_H_ */
diff --git a/drivers/platform/wmi/marshalling.c b/drivers/platform/wmi/marshalling.c
new file mode 100644
index 000000000000..63a92c4ebab5
--- /dev/null
+++ b/drivers/platform/wmi/marshalling.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI-WMI buffer marshalling.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/acpi.h>
+#include <linux/align.h>
+#include <linux/math.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+
+#include <kunit/visibility.h>
+
+#include "internal.h"
+
+static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
+{
+ size_t alignment, size;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ /*
+ * Integers are threated as 32 bit even if the ACPI DSDT
+ * declares 64 bit integer width.
+ */
+ alignment = 4;
+ size = sizeof(u32);
+ break;
+ case ACPI_TYPE_STRING:
+ /*
+ * Strings begin with a single little-endian 16-bit field containing
+ * the string length in bytes and are encoded as UTF-16LE with a terminating
+ * nul character.
+ */
+ if (obj->string.length + 1 > U16_MAX / 2)
+ return -EOVERFLOW;
+
+ alignment = 2;
+ size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
+ break;
+ case ACPI_TYPE_BUFFER:
+ /*
+ * Buffers are copied as-is.
+ */
+ alignment = 1;
+ size = obj->buffer.length;
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ *length = size_add(ALIGN(*length, alignment), size);
+
+ return 0;
+}
+
+static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
+{
+ size_t total = 0;
+ int ret;
+
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ for (int i = 0; i < obj->package.count; i++) {
+ ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
+ if (ret < 0)
+ return ret;
+ }
+ } else {
+ ret = wmi_adjust_buffer_length(&total, obj);
+ if (ret < 0)
+ return ret;
+ }
+
+ *length = total;
+
+ return 0;
+}
+
+static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
+{
+ struct wmi_string *string;
+ size_t length;
+ __le32 value;
+ u8 *aligned;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ aligned = PTR_ALIGN(buffer, 4);
+ length = sizeof(value);
+
+ value = cpu_to_le32(obj->integer.value);
+ memcpy(aligned, &value, length);
+ break;
+ case ACPI_TYPE_STRING:
+ aligned = PTR_ALIGN(buffer, 2);
+ string = (struct wmi_string *)aligned;
+ length = struct_size(string, chars, obj->string.length + 1);
+
+ /* We do not have to worry about unaligned accesses here as the WMI
+ * string will already be aligned on a two-byte boundary.
+ */
+ string->length = cpu_to_le16((obj->string.length + 1) * 2);
+ for (int i = 0; i < obj->string.length; i++)
+ string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
+
+ /*
+ * The Windows WMI-ACPI driver always emits a terminating nul character,
+ * so we emulate this behavior here as well.
+ */
+ string->chars[obj->string.length] = '\0';
+ break;
+ case ACPI_TYPE_BUFFER:
+ aligned = buffer;
+ length = obj->buffer.length;
+
+ memcpy(aligned, obj->buffer.pointer, length);
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ *consumed = (aligned - buffer) + length;
+
+ return 0;
+}
+
+static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
+{
+ size_t consumed;
+ int ret;
+
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ for (int i = 0; i < obj->package.count; i++) {
+ ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
+ &consumed);
+ if (ret < 0)
+ return ret;
+
+ buffer += consumed;
+ }
+ } else {
+ ret = wmi_obj_transform_simple(obj, buffer, &consumed);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
+{
+ size_t length, alloc_length;
+ u8 *data;
+ int ret;
+
+ ret = wmi_obj_get_buffer_length(obj, &length);
+ if (ret < 0)
+ return ret;
+
+ if (ARCH_KMALLOC_MINALIGN < 8) {
+ /*
+ * kmalloc() guarantees that the alignment of the resulting memory allocation is at
+ * least the largest power-of-two divisor of the allocation size. The WMI buffer
+ * data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
+ * integers, so we have to round the allocation size to the next multiple of 8.
+ */
+ alloc_length = round_up(length, 8);
+ } else {
+ alloc_length = length;
+ }
+
+ data = kzalloc(alloc_length, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = wmi_obj_transform(obj, data);
+ if (ret < 0) {
+ kfree(data);
+ return ret;
+ }
+
+ buffer->length = length;
+ buffer->data = data;
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
+
+int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
+{
+ const struct wmi_string *string;
+ u16 length, value;
+ size_t chars;
+ char *str;
+
+ if (buffer->length < sizeof(*string))
+ return -ENODATA;
+
+ string = buffer->data;
+ length = get_unaligned_le16(&string->length);
+ if (buffer->length < sizeof(*string) + length)
+ return -ENODATA;
+
+ /* Each character needs to be 16 bits long */
+ if (length % 2)
+ return -EINVAL;
+
+ chars = length / 2;
+ str = kmalloc(chars + 1, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ for (int i = 0; i < chars; i++) {
+ value = get_unaligned_le16(&string->chars[i]);
+
+ /* ACPI only accepts ASCII strings */
+ if (value > 0x7F) {
+ kfree(str);
+ return -EINVAL;
+ }
+
+ str[i] = value & 0xFF;
+
+ /*
+ * ACPI strings should only contain a single nul character at the end.
+ * Because of this we must not copy any padding from the WMI string.
+ */
+ if (!value) {
+ /* ACPICA wants the length of the string without the nul character */
+ out->length = i;
+ out->pointer = str;
+ return 0;
+ }
+ }
+
+ str[chars] = '\0';
+
+ out->length = chars;
+ out->pointer = str;
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);
diff --git a/drivers/platform/wmi/string.c b/drivers/platform/wmi/string.c
new file mode 100644
index 000000000000..0fc43218aa5b
--- /dev/null
+++ b/drivers/platform/wmi/string.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * WMI string utility functions.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/build_bug.h>
+#include <linux/compiler_types.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/nls.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <asm/byteorder.h>
+
+static_assert(sizeof(__le16) == sizeof(wchar_t));
+
+/**
+ * wmi_string_to_utf8s - Convert a WMI string into a UTF8 string.
+ * @str: WMI string representation
+ * @dst: Buffer to fill with UTF8 characters
+ * @length: Length of the destination buffer
+ *
+ * Convert as WMI string into a standard UTF8 string. The conversion will stop
+ * once a NUL character is detected or when the buffer is full. Any invalid UTF16
+ * characters will be ignored. The resulting UTF8 string will always be NUL-terminated
+ * when this function returns successfully.
+ *
+ * Return: Length of the resulting UTF8 string or negative errno code on failure.
+ */
+ssize_t wmi_string_to_utf8s(const struct wmi_string *str, u8 *dst, size_t length)
+{
+ /* Contains the maximum number of UTF16 code points to read */
+ int inlen = le16_to_cpu(str->length) / 2;
+ int ret;
+
+ if (length < 1)
+ return -EINVAL;
+
+ /* We must leave room for the NUL character at the end of the destination buffer */
+ ret = utf16s_to_utf8s((__force const wchar_t *)str->chars, inlen, UTF16_LITTLE_ENDIAN, dst,
+ length - 1);
+ if (ret < 0)
+ return ret;
+
+ dst[ret] = '\0';
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmi_string_to_utf8s);
+
+/**
+ * wmi_string_from_utf8s - Convert a UTF8 string into a WMI string.
+ * @str: WMI string representation
+ * @max_chars: Maximum number of UTF16 code points to store inside the WMI string
+ * @src: UTF8 string to convert
+ * @src_length: Length of the source string without any trailing NUL-characters
+ *
+ * Convert a UTF8 string into a WMI string. The conversion will stop when the WMI string is
+ * full. The resulting WMI string will always be NUL-terminated and have its length field set
+ * to and appropriate value when this function returns successfully.
+ *
+ * Return: Number of UTF16 code points inside the WMI string or negative errno code on failure.
+ */
+ssize_t wmi_string_from_utf8s(struct wmi_string *str, size_t max_chars, const u8 *src,
+ size_t src_length)
+{
+ size_t str_length;
+ int ret;
+
+ if (max_chars < 1)
+ return -EINVAL;
+
+ /* We must leave room for the NUL character at the end of the WMI string */
+ ret = utf8s_to_utf16s(src, src_length, UTF16_LITTLE_ENDIAN, (__force wchar_t *)str->chars,
+ max_chars - 1);
+ if (ret < 0)
+ return ret;
+
+ str_length = (ret + 1) * sizeof(u16);
+ if (str_length > U16_MAX)
+ return -EOVERFLOW;
+
+ str->length = cpu_to_le16(str_length);
+ str->chars[ret] = '\0';
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wmi_string_from_utf8s);
diff --git a/drivers/platform/wmi/tests/Kconfig b/drivers/platform/wmi/tests/Kconfig
new file mode 100644
index 000000000000..f7f0f3c540f5
--- /dev/null
+++ b/drivers/platform/wmi/tests/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# ACPI WMI KUnit tests
+#
+
+config ACPI_WMI_MARSHALLING_KUNIT_TEST
+ tristate "KUnit Test for ACPI-WMI marshalling" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds unit tests for the ACPI-WMI marshalling code.
+
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
+config ACPI_WMI_STRING_KUNIT_TEST
+ tristate "KUnit Test for ACPI-WMI string conversion" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds unit tests for the ACPI-WMI string conversion code.
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
diff --git a/drivers/platform/wmi/tests/Makefile b/drivers/platform/wmi/tests/Makefile
new file mode 100644
index 000000000000..62c438e26259
--- /dev/null
+++ b/drivers/platform/wmi/tests/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for linux/drivers/platform/x86/wmi/tests
+# ACPI WMI KUnit tests
+#
+
+wmi_marshalling_kunit-y := marshalling_kunit.o
+obj-$(CONFIG_ACPI_WMI_MARSHALLING_KUNIT_TEST) += wmi_marshalling_kunit.o
+
+wmi_string_kunit-y := string_kunit.o
+obj-$(CONFIG_ACPI_WMI_STRING_KUNIT_TEST) += wmi_string_kunit.o
diff --git a/drivers/platform/wmi/tests/marshalling_kunit.c b/drivers/platform/wmi/tests/marshalling_kunit.c
new file mode 100644
index 000000000000..0c7cd8774aa3
--- /dev/null
+++ b/drivers/platform/wmi/tests/marshalling_kunit.c
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit test for the ACPI-WMI marshalling code.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/acpi.h>
+#include <linux/align.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include "../internal.h"
+
+struct wmi_acpi_param {
+ const char *name;
+ const union acpi_object obj;
+ const struct wmi_buffer buffer;
+};
+
+struct wmi_string_param {
+ const char *name;
+ const char *string;
+ const struct wmi_buffer buffer;
+};
+
+struct wmi_invalid_acpi_param {
+ const char *name;
+ const union acpi_object obj;
+};
+
+struct wmi_invalid_string_param {
+ const char *name;
+ const struct wmi_buffer buffer;
+};
+
+/* 0xdeadbeef */
+static u8 expected_single_integer[] = {
+ 0xef, 0xbe, 0xad, 0xde,
+};
+
+/* "TEST" */
+static u8 expected_single_string[] = {
+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
+};
+
+static u8 test_buffer[] = {
+ 0xab, 0xcd,
+};
+
+static u8 expected_single_buffer[] = {
+ 0xab, 0xcd,
+};
+
+static union acpi_object simple_package_elements[] = {
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_buffer),
+ .pointer = test_buffer,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0x01020304,
+ },
+ },
+};
+
+static u8 expected_simple_package[] = {
+ 0xab, 0xcd,
+ 0x00, 0x00,
+ 0x04, 0x03, 0x02, 0x01,
+};
+
+static u8 test_small_buffer[] = {
+ 0xde,
+};
+
+static union acpi_object complex_package_elements[] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0xdeadbeef,
+ },
+ },
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_small_buffer),
+ .pointer = test_small_buffer,
+ },
+ },
+ {
+ .string = {
+ .type = ACPI_TYPE_STRING,
+ .length = sizeof("TEST") - 1,
+ .pointer = "TEST",
+ },
+ },
+ {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_small_buffer),
+ .pointer = test_small_buffer,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0x01020304,
+ },
+ }
+};
+
+static u8 expected_complex_package[] = {
+ 0xef, 0xbe, 0xad, 0xde,
+ 0xde,
+ 0x00,
+ 0x0a, 0x00, 0x54, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0xde,
+ 0x00,
+ 0x04, 0x03, 0x02, 0x01,
+};
+
+static const struct wmi_acpi_param wmi_acpi_params_array[] = {
+ {
+ .name = "single_integer",
+ .obj = {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = 0xdeadbeef,
+ },
+ },
+ .buffer = {
+ .data = expected_single_integer,
+ .length = sizeof(expected_single_integer),
+ },
+ },
+ {
+ .name = "single_string",
+ .obj = {
+ .string = {
+ .type = ACPI_TYPE_STRING,
+ .length = sizeof("TEST") - 1,
+ .pointer = "TEST",
+ },
+ },
+ .buffer = {
+ .data = expected_single_string,
+ .length = sizeof(expected_single_string),
+ },
+ },
+ {
+ .name = "single_buffer",
+ .obj = {
+ .buffer = {
+ .type = ACPI_TYPE_BUFFER,
+ .length = sizeof(test_buffer),
+ .pointer = test_buffer,
+ },
+ },
+ .buffer = {
+ .data = expected_single_buffer,
+ .length = sizeof(expected_single_buffer),
+ },
+ },
+ {
+ .name = "simple_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(simple_package_elements),
+ .elements = simple_package_elements,
+ },
+ },
+ .buffer = {
+ .data = expected_simple_package,
+ .length = sizeof(expected_simple_package),
+ },
+ },
+ {
+ .name = "complex_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(complex_package_elements),
+ .elements = complex_package_elements,
+ },
+ },
+ .buffer = {
+ .data = expected_complex_package,
+ .length = sizeof(expected_complex_package),
+ },
+ },
+};
+
+static void wmi_acpi_param_get_desc(const struct wmi_acpi_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object, wmi_acpi_params_array, wmi_acpi_param_get_desc);
+
+/* "WMI\0" */
+static u8 padded_wmi_string[] = {
+ 0x0a, 0x00,
+ 0x57, 0x00,
+ 0x4D, 0x00,
+ 0x49, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+};
+
+static const struct wmi_string_param wmi_string_params_array[] = {
+ {
+ .name = "test",
+ .string = "TEST",
+ .buffer = {
+ .length = sizeof(expected_single_string),
+ .data = expected_single_string,
+ },
+ },
+ {
+ .name = "padded",
+ .string = "WMI",
+ .buffer = {
+ .length = sizeof(padded_wmi_string),
+ .data = padded_wmi_string,
+ },
+ },
+};
+
+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_marshal_string, wmi_string_params_array, wmi_string_param_get_desc);
+
+static union acpi_object nested_package_elements[] = {
+ {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(simple_package_elements),
+ .elements = simple_package_elements,
+ },
+ }
+};
+
+static const struct wmi_invalid_acpi_param wmi_invalid_acpi_params_array[] = {
+ {
+ .name = "nested_package",
+ .obj = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(nested_package_elements),
+ .elements = nested_package_elements,
+ },
+ },
+ },
+ {
+ .name = "reference",
+ .obj = {
+ .reference = {
+ .type = ACPI_TYPE_LOCAL_REFERENCE,
+ .actual_type = ACPI_TYPE_ANY,
+ .handle = NULL,
+ },
+ },
+ },
+ {
+ .name = "processor",
+ .obj = {
+ .processor = {
+ .type = ACPI_TYPE_PROCESSOR,
+ .proc_id = 0,
+ .pblk_address = 0,
+ .pblk_length = 0,
+ },
+ },
+ },
+ {
+ .name = "power_resource",
+ .obj = {
+ .power_resource = {
+ .type = ACPI_TYPE_POWER,
+ .system_level = 0,
+ .resource_order = 0,
+ },
+ },
+ },
+};
+
+static void wmi_invalid_acpi_param_get_desc(const struct wmi_invalid_acpi_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_unmarshal_acpi_object_failure, wmi_invalid_acpi_params_array,
+ wmi_invalid_acpi_param_get_desc);
+
+static u8 oversized_wmi_string[] = {
+ 0x04, 0x00, 0x00, 0x00,
+};
+
+/*
+ * The error is that 3 bytes can not hold UTF-16 characters
+ * without cutting of the last one.
+ */
+static u8 undersized_wmi_string[] = {
+ 0x03, 0x00, 0x00, 0x00, 0x00,
+};
+
+static u8 non_ascii_wmi_string[] = {
+ 0x04, 0x00, 0xC4, 0x00, 0x00, 0x00,
+};
+
+static const struct wmi_invalid_string_param wmi_invalid_string_params_array[] = {
+ {
+ .name = "empty_buffer",
+ .buffer = {
+ .length = 0,
+ .data = ZERO_SIZE_PTR,
+ },
+
+ },
+ {
+ .name = "oversized",
+ .buffer = {
+ .length = sizeof(oversized_wmi_string),
+ .data = oversized_wmi_string,
+ },
+ },
+ {
+ .name = "undersized",
+ .buffer = {
+ .length = sizeof(undersized_wmi_string),
+ .data = undersized_wmi_string,
+ },
+ },
+ {
+ .name = "non_ascii",
+ .buffer = {
+ .length = sizeof(non_ascii_wmi_string),
+ .data = non_ascii_wmi_string,
+ },
+ },
+};
+
+static void wmi_invalid_string_param_get_desc(const struct wmi_invalid_string_param *param,
+ char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_marshal_string_failure, wmi_invalid_string_params_array,
+ wmi_invalid_string_param_get_desc);
+
+KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
+
+static void wmi_unmarshal_acpi_object_test(struct kunit *test)
+{
+ const struct wmi_acpi_param *param = test->param_value;
+ struct wmi_buffer result;
+ int ret;
+
+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
+ if (ret < 0)
+ KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n");
+
+ kunit_add_action(test, kfree_wrapper, result.data);
+
+ KUNIT_EXPECT_TRUE(test, IS_ALIGNED((uintptr_t)result.data, 8));
+ KUNIT_EXPECT_EQ(test, result.length, param->buffer.length);
+ KUNIT_EXPECT_MEMEQ(test, result.data, param->buffer.data, result.length);
+}
+
+static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test)
+{
+ const struct wmi_invalid_acpi_param *param = test->param_value;
+ struct wmi_buffer result;
+ int ret;
+
+ ret = wmi_unmarshal_acpi_object(&param->obj, &result);
+ if (ret < 0)
+ return;
+
+ kfree(result.data);
+ KUNIT_FAIL(test, "Invalid ACPI object was not rejected\n");
+}
+
+static void wmi_marshal_string_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ struct acpi_buffer result;
+ int ret;
+
+ ret = wmi_marshal_string(&param->buffer, &result);
+ if (ret < 0)
+ KUNIT_FAIL_AND_ABORT(test, "Marshalling of WMI string failed\n");
+
+ kunit_add_action(test, kfree_wrapper, result.pointer);
+
+ KUNIT_EXPECT_EQ(test, result.length, strlen(param->string));
+ KUNIT_EXPECT_STREQ(test, result.pointer, param->string);
+}
+
+static void wmi_marshal_string_failure_test(struct kunit *test)
+{
+ const struct wmi_invalid_string_param *param = test->param_value;
+ struct acpi_buffer result;
+ int ret;
+
+ ret = wmi_marshal_string(&param->buffer, &result);
+ if (ret < 0)
+ return;
+
+ kfree(result.pointer);
+ KUNIT_FAIL(test, "Invalid string was not rejected\n");
+}
+
+static struct kunit_case wmi_marshalling_test_cases[] = {
+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test,
+ wmi_unmarshal_acpi_object_gen_params),
+ KUNIT_CASE_PARAM(wmi_marshal_string_test,
+ wmi_marshal_string_gen_params),
+ KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_failure_test,
+ wmi_unmarshal_acpi_object_failure_gen_params),
+ KUNIT_CASE_PARAM(wmi_marshal_string_failure_test,
+ wmi_marshal_string_failure_gen_params),
+ {}
+};
+
+static struct kunit_suite wmi_marshalling_test_suite = {
+ .name = "wmi_marshalling",
+ .test_cases = wmi_marshalling_test_cases,
+};
+
+kunit_test_suite(wmi_marshalling_test_suite);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI marshalling code");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/wmi/tests/string_kunit.c b/drivers/platform/wmi/tests/string_kunit.c
new file mode 100644
index 000000000000..117f32ee26a8
--- /dev/null
+++ b/drivers/platform/wmi/tests/string_kunit.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit test for the ACPI-WMI string conversion code.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/wmi.h>
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include <asm/byteorder.h>
+
+struct wmi_string_param {
+ const char *name;
+ const struct wmi_string *wmi_string;
+ /*
+ * Remember that using sizeof() on a struct wmi_string will
+ * always return a size of two bytes due to the flexible
+ * array member!
+ */
+ size_t wmi_string_length;
+ const u8 *utf8_string;
+ size_t utf8_string_length;
+};
+
+#define TEST_WMI_STRING_LENGTH 12
+
+static const struct wmi_string test_wmi_string = {
+ .length = cpu_to_le16(10),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 test_utf8_string[] = "TEST";
+
+#define SPECIAL_WMI_STRING_LENGTH 14
+
+static const struct wmi_string special_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'Ä'),
+ cpu_to_le16(u'Ö'),
+ cpu_to_le16(u'Ü'),
+ cpu_to_le16(u'ß'),
+ cpu_to_le16(u'€'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 special_utf8_string[] = "ÄÖÜ߀";
+
+#define MULTI_POINT_WMI_STRING_LENGTH 12
+
+static const struct wmi_string multi_point_wmi_string = {
+ .length = cpu_to_le16(10),
+ .chars = {
+ cpu_to_le16(u'K'),
+ /* 🐧 */
+ cpu_to_le16(0xD83D),
+ cpu_to_le16(0xDC27),
+ cpu_to_le16(u'!'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 multi_point_utf8_string[] = "K🐧!";
+
+#define PADDED_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string padded_test_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 padded_test_utf8_string[] = "TEST\0";
+
+#define OVERSIZED_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string oversized_test_wmi_string = {
+ .length = cpu_to_le16(8),
+ .chars = {
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'!'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+static const u8 oversized_test_utf8_string[] = "TEST!";
+
+#define INVALID_TEST_WMI_STRING_LENGTH 14
+
+static const struct wmi_string invalid_test_wmi_string = {
+ .length = cpu_to_le16(12),
+ .chars = {
+ cpu_to_le16(u'T'),
+ /* 🐧, with low surrogate missing */
+ cpu_to_le16(0xD83D),
+ cpu_to_le16(u'E'),
+ cpu_to_le16(u'S'),
+ cpu_to_le16(u'T'),
+ cpu_to_le16(u'\0'),
+ },
+};
+
+/* We have to split the string here to end the hex escape sequence */
+static const u8 invalid_test_utf8_string[] = "T" "\xF0\x9F" "EST";
+
+static const struct wmi_string_param wmi_string_params_array[] = {
+ {
+ .name = "ascii_string",
+ .wmi_string = &test_wmi_string,
+ .wmi_string_length = TEST_WMI_STRING_LENGTH,
+ .utf8_string = test_utf8_string,
+ .utf8_string_length = sizeof(test_utf8_string),
+ },
+ {
+ .name = "special_string",
+ .wmi_string = &special_wmi_string,
+ .wmi_string_length = SPECIAL_WMI_STRING_LENGTH,
+ .utf8_string = special_utf8_string,
+ .utf8_string_length = sizeof(special_utf8_string),
+ },
+ {
+ .name = "multi_point_string",
+ .wmi_string = &multi_point_wmi_string,
+ .wmi_string_length = MULTI_POINT_WMI_STRING_LENGTH,
+ .utf8_string = multi_point_utf8_string,
+ .utf8_string_length = sizeof(multi_point_utf8_string),
+ },
+};
+
+static void wmi_string_param_get_desc(const struct wmi_string_param *param, char *desc)
+{
+ strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(wmi_string, wmi_string_params_array, wmi_string_param_get_desc);
+
+static void wmi_string_to_utf8s_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ ssize_t ret;
+ u8 *result;
+
+ result = kunit_kzalloc(test, param->utf8_string_length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_to_utf8s(param->wmi_string, result, param->utf8_string_length);
+
+ KUNIT_EXPECT_EQ(test, ret, param->utf8_string_length - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, param->utf8_string, param->utf8_string_length);
+}
+
+static void wmi_string_from_utf8s_test(struct kunit *test)
+{
+ const struct wmi_string_param *param = test->param_value;
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (param->wmi_string_length - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, param->wmi_string_length, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, param->utf8_string,
+ param->utf8_string_length);
+
+ KUNIT_EXPECT_EQ(test, ret, max_chars - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, param->wmi_string, param->wmi_string_length);
+}
+
+static void wmi_string_to_utf8s_padded_test(struct kunit *test)
+{
+ u8 result[sizeof(padded_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&padded_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_padded_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (PADDED_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, PADDED_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, padded_test_utf8_string,
+ sizeof(padded_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
+}
+
+static void wmi_string_to_utf8s_oversized_test(struct kunit *test)
+{
+ u8 result[sizeof(oversized_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&oversized_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_oversized_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, oversized_test_utf8_string,
+ sizeof(oversized_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, &test_wmi_string, sizeof(test_wmi_string));
+}
+
+static void wmi_string_to_utf8s_invalid_test(struct kunit *test)
+{
+ u8 result[sizeof(invalid_test_utf8_string)];
+ ssize_t ret;
+
+ ret = wmi_string_to_utf8s(&invalid_test_wmi_string, result, sizeof(result));
+
+ KUNIT_EXPECT_EQ(test, ret, sizeof(test_utf8_string) - 1);
+ KUNIT_EXPECT_MEMEQ(test, result, test_utf8_string, sizeof(test_utf8_string));
+}
+
+static void wmi_string_from_utf8s_invalid_test(struct kunit *test)
+{
+ struct wmi_string *result;
+ size_t max_chars;
+ ssize_t ret;
+
+ max_chars = (INVALID_TEST_WMI_STRING_LENGTH - sizeof(*result)) / 2;
+ result = kunit_kzalloc(test, INVALID_TEST_WMI_STRING_LENGTH, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result);
+
+ ret = wmi_string_from_utf8s(result, max_chars, invalid_test_utf8_string,
+ sizeof(invalid_test_utf8_string));
+
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static struct kunit_case wmi_string_test_cases[] = {
+ KUNIT_CASE_PARAM(wmi_string_to_utf8s_test, wmi_string_gen_params),
+ KUNIT_CASE_PARAM(wmi_string_from_utf8s_test, wmi_string_gen_params),
+ KUNIT_CASE(wmi_string_to_utf8s_padded_test),
+ KUNIT_CASE(wmi_string_from_utf8s_padded_test),
+ KUNIT_CASE(wmi_string_to_utf8s_oversized_test),
+ KUNIT_CASE(wmi_string_from_utf8s_oversized_test),
+ KUNIT_CASE(wmi_string_to_utf8s_invalid_test),
+ KUNIT_CASE(wmi_string_from_utf8s_invalid_test),
+ {}
+};
+
+static struct kunit_suite wmi_string_test_suite = {
+ .name = "wmi_string",
+ .test_cases = wmi_string_test_cases,
+};
+
+kunit_test_suite(wmi_string_test_suite);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("KUnit test for the ACPI-WMI string conversion code");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index 13c4fec2c7ef..3d94b03cf794 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -9,6 +9,9 @@
*/
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
#include "pmf.h"
#define APMF_CQL_NOTIFICATION 2
@@ -331,6 +334,39 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req
req, sizeof(*req));
}
+/* Store custom BIOS inputs data in ring buffer */
+static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
+{
+ struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
+ int i;
+
+ guard(mutex)(&pmf_dev->cbi_mutex);
+
+ switch (pmf_dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req1.pending_req;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req.pending_req;
+ break;
+ default:
+ return;
+ }
+
+ if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
+ /* Rare case: ensures the newest BIOS input value is kept */
+ dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+ }
+
+ rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+}
+
static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
{
if (!pdev->cb_flag)
@@ -356,6 +392,8 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
@@ -374,6 +412,8 @@ static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 8fc293c9c538..b9e5a2cf3aae 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -8,12 +8,16 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
+#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
@@ -53,6 +57,12 @@ 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 bool smart_pc_support = true;
+module_param(smart_pc_support, bool, 0444);
+MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
+
+static struct device *pmf_device;
+
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier);
@@ -314,6 +324,126 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
return 0;
}
+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
+{
+ int ret, i;
+
+ guard(mutex)(&dev->metrics_mutex);
+
+ ret = is_npu_metrics_supported(dev);
+ if (ret)
+ return ret;
+
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ memset(dev->buf, 0, dev->mtable_size);
+
+ /* Send SMU command to get NPU metrics */
+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ if (ret) {
+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+
+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
+ data->npu_power = dev->m_table_v2.npu_power;
+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
+ data->npu_reads = dev->m_table_v2.npu_reads;
+ data->npu_writes = dev->m_table_v2.npu_writes;
+
+ return 0;
+}
+
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
+{
+ struct amd_pmf_dev *pdev;
+
+ if (!info)
+ return -EINVAL;
+
+ if (!pmf_device)
+ return -ENODEV;
+
+ pdev = dev_get_drvdata(pmf_device);
+ if (!pdev)
+ return -ENODEV;
+
+ return amd_pmf_get_smu_metrics(pdev, info);
+}
+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
+
+static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
+{
+ bool status;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
+ ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
+ if (ret) {
+ dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
+ return ret;
+ }
+
+ ret = amd_pmf_start_policy_engine(pdev);
+ dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
+ status = ret == TA_PMF_TYPE_SUCCESS;
+ if (status)
+ break;
+ amd_pmf_tee_deinit(pdev);
+ }
+
+ return 0;
+}
+
+static int amd_pmf_restore_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+ int ret;
+
+ if (pdev->buf) {
+ ret = amd_pmf_set_dram_addr(pdev, false);
+ if (ret)
+ return ret;
+ }
+
+ if (pdev->smart_pc_enabled)
+ amd_pmf_reinit_ta(pdev);
+
+ return 0;
+}
+
+static int amd_pmf_freeze_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+
+ if (!pdev->smart_pc_enabled)
+ return 0;
+
+ cancel_delayed_work_sync(&pdev->pb_work);
+ /* Clear all TEE resources */
+ amd_pmf_tee_deinit(pdev);
+ pdev->session_id = 0;
+
+ return 0;
+}
+
static int amd_pmf_suspend_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
@@ -347,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler);
+static const struct dev_pm_ops amd_pmf_pm = {
+ .suspend = amd_pmf_suspend_handler,
+ .resume = amd_pmf_resume_handler,
+ .freeze = amd_pmf_freeze_handler,
+ .restore = amd_pmf_restore_handler,
+};
static void amd_pmf_init_features(struct amd_pmf_dev *dev)
{
@@ -362,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
}
- amd_pmf_init_smart_pc(dev);
- if (dev->smart_pc_enabled) {
- dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
- /* If Smart PC is enabled, no need to check for other features */
- return;
+ if (smart_pc_support) {
+ amd_pmf_init_smart_pc(dev);
+ if (dev->smart_pc_enabled) {
+ dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
+ /* If Smart PC is enabled, no need to check for other features */
+ return;
+ }
+ } else {
+ dev->smart_pc_enabled = false;
}
if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
@@ -477,6 +616,14 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (err)
return err;
+ err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
+ if (err)
+ return err;
+
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
@@ -485,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
+ pmf_device = dev->dev;
+
dev_info(dev->dev, "registered PMF device successfully\n");
return 0;
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 9144c8c3bbaf..69fef7448744 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -12,7 +12,10 @@
#define PMF_H
#include <linux/acpi.h>
+#include <linux/amd-pmf-io.h>
+#include <linux/circ_buf.h>
#include <linux/input.h>
+#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
@@ -120,6 +123,7 @@ struct cookie_header {
#define APTS_MAX_STATES 16
#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
#define BIOS_INPUTS_MAX 10
+#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
/* amd_pmf_send_cmd() set/get */
#define SET_CMD false
@@ -129,6 +133,12 @@ struct cookie_header {
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
+static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
+ 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
+ UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
+ 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
+ };
+
/* APTS PMF BIOS Interface */
struct amd_pmf_apts_output {
u16 table_version;
@@ -365,6 +375,22 @@ struct pmf_bios_inputs_prev {
u32 custom_bios_inputs[BIOS_INPUTS_MAX];
};
+/**
+ * struct pmf_bios_input_entry - Snapshot of custom BIOS input event
+ * @val: Array of custom BIOS input values
+ * @preq: Pending request value associated with this event
+ */
+struct pmf_bios_input_entry {
+ u32 val[BIOS_INPUTS_MAX];
+ u32 preq;
+};
+
+struct pmf_cbi_ring_buffer {
+ struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
+ int head;
+ int tail;
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -413,6 +439,9 @@ struct amd_pmf_dev {
struct apmf_sbios_req_v1 req1;
struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
bool cb_flag; /* To handle first custom BIOS input */
+ struct pmf_cbi_ring_buffer cbi_buf;
+ struct mutex cbi_mutex; /* Protects ring buffer access */
+ struct mutex metrics_mutex;
};
struct apmf_sps_prop_granular_v2 {
@@ -895,4 +924,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
+
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index 0a37dc6a7950..f48678a23cc7 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -11,6 +11,7 @@
#include <acpi/button.h>
#include <linux/amd-pmf-io.h>
+#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/units.h>
#include "pmf.h"
@@ -132,32 +133,39 @@ static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int
}
}
-static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req,
+static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
const struct amd_pmf_pb_bitmap *inputs,
- const u32 *custom_policy, struct ta_pmf_enact_table *in)
+ struct ta_pmf_enact_table *in)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
- if (!(pending_req & inputs[i].bit_mask))
+ if (!(data->preq & inputs[i].bit_mask))
continue;
- amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]);
- pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i];
- dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]);
+ amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
+ pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
+ dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
}
}
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
struct ta_pmf_enact_table *in)
{
+ struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
unsigned int i;
+ guard(mutex)(&pdev->cbi_mutex);
+
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
- if (!(pdev->req.pending_req || pdev->req1.pending_req))
+ if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
return;
+ /* If no active custom BIOS input pending request, do not consume further work */
+ if (!rb->data[rb->tail].preq)
+ goto out_rbadvance;
+
if (!pdev->smart_pc_enabled)
return;
@@ -165,20 +173,17 @@ static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
case PMF_IF_V1:
if (!is_apmf_bios_input_notifications_supported(pdev))
return;
- amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1,
- pdev->req1.custom_policy, in);
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
break;
case PMF_IF_V2:
- amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs,
- pdev->req.custom_policy, in);
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
break;
default:
break;
}
- /* Clear pending requests after handling */
- memset(&pdev->req, 0, sizeof(pdev->req));
- memset(&pdev->req1, 0, sizeof(pdev->req1));
+out_rbadvance:
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index 0abce76f89ff..7ccd93f506b2 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444);
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
#endif
-static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
- 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
- UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
- 0x29, 0xb1, 0x3d, 0x85, 0x43),
- };
-
static const char *amd_pmf_uevent_as_str(unsigned int state)
{
switch (state) {
@@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work)
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
}
-static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
{
struct cookie_header *header;
int res;
@@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
return 0;
}
-static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
{
u32 size;
int ret;
@@ -528,7 +522,7 @@ out_ctx:
return ret;
}
-static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
{
if (!dev->tee_ctx)
return;
@@ -591,6 +585,8 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
status = ret == TA_PMF_TYPE_SUCCESS;
if (status) {
dev->cb_flag = true;
+ dev->cbi_buf.head = 0;
+ dev->cbi_buf.tail = 0;
break;
}
amd_pmf_tee_deinit(dev);
diff --git a/drivers/platform/x86/amd/wbrf.c b/drivers/platform/x86/amd/wbrf.c
index 0f58d252b620..dc10d12bc80d 100644
--- a/drivers/platform/x86/amd/wbrf.c
+++ b/drivers/platform/x86/amd/wbrf.c
@@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in)
{
union acpi_object argv4;
- union acpi_object *tmp;
- union acpi_object *obj;
u32 num_of_ranges = 0;
u32 num_of_elements;
u32 arg_idx = 0;
@@ -74,7 +72,7 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
*/
num_of_elements = 2 * num_of_ranges + 2;
- tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
+ union acpi_object *tmp __free(kfree) = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
@@ -101,26 +99,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
tmp[arg_idx++].integer.value = in->band_list[i].end;
}
- obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
- WBRF_REVISION, WBRF_RECORD, &argv4);
+ union acpi_object *obj __free(kfree) =
+ acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
+ WBRF_REVISION, WBRF_RECORD, &argv4);
- if (!obj) {
- kfree(tmp);
+ if (!obj)
return -EINVAL;
- }
- if (obj->type != ACPI_TYPE_INTEGER) {
- ret = -EINVAL;
- goto out;
- }
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
ret = obj->integer.value;
if (ret)
- ret = -EINVAL;
-
-out:
- ACPI_FREE(obj);
- kfree(tmp);
+ return -EINVAL;
return ret;
}
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 0775fadedd10..275b56d6a09f 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -31,13 +31,13 @@
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
-#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -256,6 +256,9 @@ struct asus_wmi {
int tpd_led_wk;
struct led_classdev kbd_led;
int kbd_led_wk;
+ bool kbd_led_notify;
+ bool kbd_led_avail;
+ bool kbd_led_registered;
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
@@ -264,6 +267,7 @@ struct asus_wmi {
struct work_struct tpd_led_work;
struct work_struct wlan_led_work;
struct work_struct lightbar_led_work;
+ struct work_struct kbd_led_work;
struct asus_rfkill wlan;
struct asus_rfkill bluetooth;
@@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
/* LEDs ***********************************************************************/
+struct asus_hid_ref {
+ struct list_head listeners;
+ struct asus_wmi *asus;
+ /* Protects concurrent access from hid-asus and asus-wmi to leds */
+ spinlock_t lock;
+};
+
+static struct asus_hid_ref asus_ref = {
+ .listeners = LIST_HEAD_INIT(asus_ref.listeners),
+ .asus = NULL,
+ /*
+ * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
+ * asus variables are read-only after .asus is set.
+ *
+ * The led cdev device is not protected because it calls backlight_get
+ * during initialization, which would result in a nested lock attempt.
+ *
+ * The led cdev is safe to access without a lock because if
+ * kbd_led_avail is true it is initialized before .asus is set and never
+ * changed until .asus is dropped. If kbd_led_avail is false, the led
+ * cdev is registered by the workqueue, which is single-threaded and
+ * cancelled before asus-wmi would access the led cdev to unregister it.
+ *
+ * A spinlock is used, because the protected variables can be accessed
+ * from an IRQ context from asus-hid.
+ */
+ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
+};
+
+/*
+ * Allows registering hid-asus listeners that want to be notified of
+ * keyboard backlight changes.
+ */
+int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+ struct asus_wmi *asus;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_add_tail(&bdev->list, &asus_ref.listeners);
+ asus = asus_ref.asus;
+ if (asus)
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_register_listener);
+
+/*
+ * Allows unregistering hid-asus listeners that were added with
+ * asus_hid_register_listener().
+ */
+void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_del(&bdev->list);
+}
+EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
+
+static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
+
+static void kbd_led_update_all(struct work_struct *work)
+{
+ struct asus_wmi *asus;
+ bool registered, notify;
+ int ret, value;
+
+ asus = container_of(work, struct asus_wmi, kbd_led_work);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ registered = asus->kbd_led_registered;
+ value = asus->kbd_led_wk;
+ notify = asus->kbd_led_notify;
+ }
+
+ if (!registered) {
+ /*
+ * This workqueue runs under asus-wmi, which means probe has
+ * completed and asus-wmi will keep running until it finishes.
+ * Therefore, we can safely register the LED without holding
+ * a spinlock.
+ */
+ ret = devm_led_classdev_register(&asus->platform_device->dev,
+ &asus->kbd_led);
+ if (!ret) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_registered = true;
+ } else {
+ pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
+ return;
+ }
+ }
+
+ if (value >= 0)
+ do_kbd_led_set(&asus->kbd_led, value);
+ if (notify) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_notify = false;
+ led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
+ }
+}
+
+/*
+ * This function is called from hid-asus to inform asus-wmi of brightness
+ * changes initiated by the keyboard backlight keys.
+ */
+int asus_hid_event(enum asus_hid_event event)
+{
+ struct asus_wmi *asus;
+ int brightness;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ asus = asus_ref.asus;
+ if (!asus || !asus->kbd_led_registered)
+ return -EBUSY;
+
+ brightness = asus->kbd_led_wk;
+
+ switch (event) {
+ case ASUS_EV_BRTUP:
+ brightness += 1;
+ break;
+ case ASUS_EV_BRTDOWN:
+ brightness -= 1;
+ break;
+ case ASUS_EV_BRTTOGGLE:
+ if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
+ brightness = 0;
+ else
+ brightness += 1;
+ break;
+ }
+
+ asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
+ asus->kbd_led_notify = true;
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_event);
+
/*
* These functions actually update the LED's, and are called from a
* workqueue. By doing this as separate work rather than when the LED
@@ -1661,7 +1803,8 @@ static void kbd_led_update(struct asus_wmi *asus)
{
int ctrl_param = 0;
- ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}
@@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
+ struct asus_hid_listener *listener;
struct asus_wmi *asus;
- int max_level;
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
- max_level = asus->kbd_led.max_brightness;
- asus->kbd_led_wk = clamp_val(value, 0, max_level);
- kbd_led_update(asus);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
+
+ if (asus->kbd_led_avail)
+ kbd_led_update(asus);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ list_for_each_entry(listener, &asus_ref.listeners, list)
+ listener->brightness_set(listener, asus->kbd_led_wk);
+ }
}
static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
@@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
{
- struct led_classdev *led_cdev = &asus->kbd_led;
-
- do_kbd_led_set(led_cdev, value);
- led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus->kbd_led_wk = value;
+ asus->kbd_led_notify = true;
+ }
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
@@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ if (!asus->kbd_led_avail)
+ return asus->kbd_led_wk;
+ }
+
retval = kbd_led_read(asus, &value, NULL);
if (retval < 0)
return retval;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = value;
+
return value;
}
@@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
static void asus_wmi_led_exit(struct asus_wmi *asus)
{
- led_classdev_unregister(&asus->kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus_ref.asus = NULL;
+
led_classdev_unregister(&asus->tpd_led);
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
@@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
- if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
- pr_info("using asus-wmi for asus::kbd_backlight\n");
- asus->kbd_led_wk = led_val;
- asus->kbd_led.name = "asus::kbd_backlight";
- asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
- asus->kbd_led.brightness_set_blocking = kbd_led_set;
- asus->kbd_led.brightness_get = kbd_led_get;
- asus->kbd_led.max_brightness = 3;
+ asus->kbd_led.name = "asus::kbd_backlight";
+ asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
+ asus->kbd_led.brightness_set_blocking = kbd_led_set;
+ asus->kbd_led.brightness_get = kbd_led_get;
+ asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
+ asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
+ INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
+ if (asus->kbd_led_avail) {
+ asus->kbd_led_wk = led_val;
if (num_rgb_groups != 0)
asus->kbd_led.groups = kbd_rgb_mode_groups;
+ } else {
+ asus->kbd_led_wk = -1;
+ }
- rv = led_classdev_register(&asus->platform_device->dev,
- &asus->kbd_led);
- if (rv)
- goto error;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus_ref.asus = asus;
+ if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
@@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
{
+ enum led_brightness led_value;
unsigned int key_value = 1;
bool autorelease = 1;
@@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ led_value = asus->kbd_led_wk;
+
if (code == NOTIFY_KBD_BRTUP) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
if (code == NOTIFY_KBD_BRTDWN) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
+ kbd_led_set_by_kbd(asus, led_value - 1);
return;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
- if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
+ if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
kbd_led_set_by_kbd(asus, 0);
else
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index f4ea1ea05997..304d9ac63c8a 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -13,23 +13,28 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/kernel.h>
-#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dmi.h>
+#include <linux/fixp-arith.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/types.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
-#include <linux/hwmon.h>
-#include <linux/acpi.h>
-#include <linux/mutex.h>
-#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
+#include <linux/slab.h>
#include <linux/string.h>
-#include <linux/dmi.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
MODULE_DESCRIPTION("HP laptop WMI driver");
@@ -41,9 +46,13 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
-#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62
-#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
-#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
+enum hp_ec_offsets {
+ HP_EC_OFFSET_UNKNOWN = 0x00,
+ HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59,
+ HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62,
+ HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63,
+ HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95,
+};
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
@@ -53,6 +62,70 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
+enum hp_thermal_profile_omen_v0 {
+ HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
+};
+
+enum hp_thermal_profile_omen_v1 {
+ HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
+ HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
+ HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
+};
+
+enum hp_thermal_profile_omen_flags {
+ HP_OMEN_EC_FLAGS_TURBO = 0x04,
+ HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
+ HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
+};
+
+enum hp_thermal_profile_victus {
+ HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+enum hp_thermal_profile_victus_s {
+ HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
+};
+
+enum hp_thermal_profile {
+ HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
+ HP_THERMAL_PROFILE_DEFAULT = 0x01,
+ HP_THERMAL_PROFILE_COOL = 0x02,
+ HP_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+
+struct thermal_profile_params {
+ u8 performance;
+ u8 balanced;
+ u8 low_power;
+ u8 ec_tp_offset;
+};
+
+static const struct thermal_profile_params victus_s_thermal_params = {
+ .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_EC_OFFSET_UNKNOWN,
+};
+
+static const struct thermal_profile_params omen_v1_thermal_params = {
+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET,
+};
+
+/*
+ * A generic pointer for the currently-active board's thermal profile
+ * parameters.
+ */
+static struct thermal_profile_params *active_thermal_profile_params;
+
/* DMI board names of devices that should use the omen specific path for
* thermal profiles.
* This was obtained by taking a look in the windows omen command center
@@ -99,12 +172,40 @@ static const char * const victus_thermal_profile_boards[] = {
};
/* DMI Board names of Victus 16-r and Victus 16-s laptops */
-static const char * const victus_s_thermal_profile_boards[] = {
- "8BBE", "8BD4", "8BD5",
- "8C78", "8C99", "8C9C",
- "8D41",
+static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {},
};
+static bool is_victus_s_board;
+
enum hp_wmi_radio {
HPWMI_WIFI = 0x0,
HPWMI_BLUETOOTH = 0x1,
@@ -190,7 +291,8 @@ enum hp_wmi_gm_commandtype {
HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22,
HPWMI_SET_POWER_LIMITS_QUERY = 0x29,
HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D,
- HPWMI_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F,
};
enum hp_wmi_command {
@@ -225,42 +327,6 @@ enum hp_wireless2_bits {
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
};
-enum hp_thermal_profile_omen_v0 {
- HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
-};
-
-enum hp_thermal_profile_omen_v1 {
- HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
- HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
- HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
-};
-
-enum hp_thermal_profile_omen_flags {
- HP_OMEN_EC_FLAGS_TURBO = 0x04,
- HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
- HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
-};
-
-enum hp_thermal_profile_victus {
- HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
-};
-
-enum hp_thermal_profile_victus_s {
- HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
-};
-
-enum hp_thermal_profile {
- HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
- HP_THERMAL_PROFILE_DEFAULT = 0x01,
- HP_THERMAL_PROFILE_COOL = 0x02,
- HP_THERMAL_PROFILE_QUIET = 0x03,
-};
-
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
@@ -348,6 +414,58 @@ static const char * const tablet_chassis_types[] = {
#define DEVICE_MODE_TABLET 0x06
+#define CPU_FAN 0
+#define GPU_FAN 1
+
+enum pwm_modes {
+ PWM_MODE_MAX = 0,
+ PWM_MODE_MANUAL = 1,
+ PWM_MODE_AUTO = 2,
+};
+
+struct hp_wmi_hwmon_priv {
+ u8 min_rpm;
+ u8 max_rpm;
+ u8 gpu_delta;
+ u8 mode;
+ u8 pwm;
+ struct delayed_work keep_alive_dwork;
+};
+
+struct victus_s_fan_table_header {
+ u8 unknown;
+ u8 num_entries;
+} __packed;
+
+struct victus_s_fan_table_entry {
+ u8 cpu_rpm;
+ u8 gpu_rpm;
+ u8 unknown;
+} __packed;
+
+struct victus_s_fan_table {
+ struct victus_s_fan_table_header header;
+ struct victus_s_fan_table_entry entries[];
+} __packed;
+
+/*
+ * 90s delay to prevent the firmware from resetting fan mode after fixed
+ * 120s timeout
+ */
+#define KEEP_ALIVE_DELAY_SECS 90
+
+static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX,
+ clamp_val(rpm, 0, priv->max_rpm));
+}
+
+static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm,
+ clamp_val(pwm, 0, U8_MAX));
+}
+
/* map output size to the corresponding WMI method id */
static inline int encode_outsize_for_pvsz(int outsize)
{
@@ -637,41 +755,53 @@ static int hp_wmi_fan_speed_max_set(int enabled)
return enabled;
}
-static int hp_wmi_fan_speed_reset(void)
+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
{
- u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
- int ret;
+ u8 fan_speed[2];
+ int gpu_speed, ret;
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
- &fan_speed, sizeof(fan_speed), 0);
+ fan_speed[CPU_FAN] = speed;
+ fan_speed[GPU_FAN] = speed;
- return ret;
-}
-
-static int hp_wmi_fan_speed_max_reset(void)
-{
- int ret;
+ /*
+ * GPU fan speed is always a little higher than CPU fan speed, we fetch
+ * this delta value from the fan table during hwmon init.
+ * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
+ * automatic mode.
+ */
+ if (speed != HP_FAN_SPEED_AUTOMATIC) {
+ gpu_speed = speed + priv->gpu_delta;
+ fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
+ }
+ ret = hp_wmi_get_fan_count_userdefine_trigger();
+ if (ret < 0)
+ return ret;
+ /* Max fans need to be explicitly disabled */
ret = hp_wmi_fan_speed_max_set(0);
- if (ret)
+ if (ret < 0)
return ret;
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM,
+ &fan_speed, sizeof(fan_speed), 0);
- /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
- ret = hp_wmi_fan_speed_reset();
return ret;
}
-static int hp_wmi_fan_speed_max_get(void)
+static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
{
- int val = 0, ret;
+ return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
+}
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
- &val, zero_if_sup(val), sizeof(val));
+static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+ ret = hp_wmi_fan_speed_max_set(0);
if (ret)
- return ret < 0 ? ret : -EINVAL;
+ return ret;
- return val;
+ /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
+ return hp_wmi_fan_speed_reset(priv);
}
static int __init hp_wmi_bios_2008_later(void)
@@ -1581,15 +1711,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile)
static bool is_victus_s_thermal_profile(void)
{
- const char *board_name;
-
- board_name = dmi_get_system_info(DMI_BOARD_NAME);
- if (!board_name)
- return false;
-
- return match_string(victus_s_thermal_profile_boards,
- ARRAY_SIZE(victus_s_thermal_profile_boards),
- board_name) >= 0;
+ /* Initialised in driver init, hence safe to use here */
+ return is_victus_s_board;
}
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
@@ -1670,27 +1793,86 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2)
return ret;
}
+static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile)
+{
+ int ret = 0;
+ bool current_ctgp_state, current_ppab_state;
+ u8 current_dstate, current_gpu_slowdown_temp, tp;
+ const struct thermal_profile_params *params;
+
+ params = active_thermal_profile_params;
+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ *profile = active_platform_profile;
+ return 0;
+ }
+
+ ret = ec_read(params->ec_tp_offset, &tp);
+ if (ret)
+ return ret;
+
+ /*
+ * We cannot use active_thermal_profile_params here, because boards
+ * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
+ * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
+ * Gaming Hub on windows. Hence accept both of these values.
+ */
+ if (tp == victus_s_thermal_params.performance ||
+ tp == omen_v1_thermal_params.performance) {
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ } else if (tp == victus_s_thermal_params.balanced ||
+ tp == omen_v1_thermal_params.balanced) {
+ /*
+ * Since both PLATFORM_PROFILE_LOW_POWER and
+ * PLATFORM_PROFILE_BALANCED share the same thermal profile
+ * parameter value, hence to differentiate between them, we
+ * query the GPU CTGP and PPAB states and compare based off of
+ * that.
+ */
+ ret = victus_s_gpu_thermal_profile_get(&current_ctgp_state,
+ &current_ppab_state,
+ &current_dstate,
+ &current_gpu_slowdown_temp);
+ if (ret < 0)
+ return ret;
+ if (current_ctgp_state == 0 && current_ppab_state == 0)
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ else if (current_ctgp_state == 0 && current_ppab_state == 1)
+ *profile = PLATFORM_PROFILE_BALANCED;
+ else
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
{
+ struct thermal_profile_params *params;
bool gpu_ctgp_enable, gpu_ppab_enable;
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
int err, tp;
+ params = active_thermal_profile_params;
+ if (!params)
+ return -ENODEV;
+
switch (profile) {
case PLATFORM_PROFILE_PERFORMANCE:
- tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
+ tp = params->performance;
gpu_ctgp_enable = true;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_BALANCED:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->balanced;
gpu_ctgp_enable = false;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_LOW_POWER:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->low_power;
gpu_ctgp_enable = false;
gpu_ppab_enable = false;
gpu_dstate = 1;
@@ -1832,6 +2014,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
void *data)
{
struct acpi_bus_event *event_entry = data;
+ enum platform_profile_option actual_profile;
int err;
if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
@@ -1839,6 +2022,17 @@ static int victus_s_powersource_event(struct notifier_block *nb,
pr_debug("Received power source device event\n");
+ guard(mutex)(&active_platform_profile_lock);
+ err = platform_profile_victus_s_get_ec(&actual_profile);
+ if (err < 0) {
+ /*
+ * Although we failed to get the current platform profile, we
+ * still want the other event consumers to process it.
+ */
+ pr_warn("Failed to read current platform profile (%d)\n", err);
+ return NOTIFY_DONE;
+ }
+
/*
* Switching to battery power source while Performance mode is active
* needs manual triggering of CPU power limits. Same goes when switching
@@ -1847,7 +2041,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
* Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
*/
- if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
+ if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) {
pr_debug("Triggering CPU PL1/PL2 actualization\n");
err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
HP_POWER_LIMIT_DEFAULT);
@@ -1958,11 +2152,22 @@ static int thermal_profile_setup(struct platform_device *device)
ops = &platform_profile_victus_ops;
} else if (is_victus_s_thermal_profile()) {
/*
- * Being unable to retrieve laptop's current thermal profile,
- * during this setup, we set it to Balanced by default.
+ * For an unknown EC layout board, platform_profile_victus_s_get_ec(),
+ * behaves like a wrapper around active_platform_profile, to avoid using
+ * uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
*/
- active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ } else {
+ err = platform_profile_victus_s_get_ec(&active_platform_profile);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * call thermal profile write command to ensure that the
+ * firmware correctly sets the OEM variables
+ */
err = platform_profile_victus_s_set_ec(active_platform_profile);
if (err < 0)
return err;
@@ -2031,6 +2236,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
static void __exit hp_wmi_bios_remove(struct platform_device *device)
{
int i;
+ struct hp_wmi_hwmon_priv *priv;
for (i = 0; i < rfkill2_count; i++) {
rfkill_unregister(rfkill2[i].rfkill);
@@ -2049,6 +2255,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
rfkill_unregister(wwan_rfkill);
rfkill_destroy(wwan_rfkill);
}
+
+ priv = platform_get_drvdata(device);
+ if (priv)
+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
}
static int hp_wmi_resume_handler(struct device *device)
@@ -2108,12 +2318,56 @@ static struct platform_driver hp_wmi_driver __refdata = {
.remove = __exit_p(hp_wmi_bios_remove),
};
+static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+
+ switch (priv->mode) {
+ case PWM_MODE_MAX:
+ if (is_victus_s_thermal_profile())
+ hp_wmi_get_fan_count_userdefine_trigger();
+ ret = hp_wmi_fan_speed_max_set(1);
+ if (ret < 0)
+ return ret;
+ schedule_delayed_work(&priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
+ if (ret < 0)
+ return ret;
+ schedule_delayed_work(&priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_AUTO:
+ if (is_victus_s_thermal_profile()) {
+ hp_wmi_get_fan_count_userdefine_trigger();
+ ret = hp_wmi_fan_speed_max_reset(priv);
+ } else {
+ ret = hp_wmi_fan_speed_max_set(0);
+ }
+ if (ret < 0)
+ return ret;
+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
+ return 0;
+ default:
+ /* shouldn't happen */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static umode_t hp_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
+ return 0;
return 0644;
case hwmon_fan:
if (is_victus_s_thermal_profile()) {
@@ -2134,8 +2388,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
- int ret;
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm, ret;
+ priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
if (is_victus_s_thermal_profile())
@@ -2147,16 +2403,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
*val = ret;
return 0;
case hwmon_pwm:
- switch (hp_wmi_fan_speed_max_get()) {
- case 0:
- /* 0 is automatic fan, which is 2 for hwmon */
- *val = 2;
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ *val = rpm_to_pwm(rpm / 100, priv);
return 0;
- case 1:
- /* 1 is max fan, which is 0
- * (no fan speed control) for hwmon
- */
- *val = 0;
+ }
+ switch (priv->mode) {
+ case PWM_MODE_MAX:
+ case PWM_MODE_MANUAL:
+ case PWM_MODE_AUTO:
+ *val = priv->mode;
return 0;
default:
/* shouldn't happen */
@@ -2170,23 +2431,46 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm;
+
+ priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /* PWM input is invalid when not in manual mode */
+ if (priv->mode != PWM_MODE_MANUAL)
+ return -EINVAL;
+
+ /* ensure PWM input is within valid fan speeds */
+ rpm = pwm_to_rpm(val, priv);
+ rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
+ priv->pwm = rpm_to_pwm(rpm, priv);
+ return hp_wmi_apply_fan_settings(priv);
+ }
switch (val) {
- case 0:
- if (is_victus_s_thermal_profile())
- hp_wmi_get_fan_count_userdefine_trigger();
- /* 0 is no fan speed control (max), which is 1 for us */
- return hp_wmi_fan_speed_max_set(1);
- case 2:
- /* 2 is automatic speed control, which is 0 for us */
- if (is_victus_s_thermal_profile()) {
- hp_wmi_get_fan_count_userdefine_trigger();
- return hp_wmi_fan_speed_max_reset();
- } else
- return hp_wmi_fan_speed_max_set(0);
+ case PWM_MODE_MAX:
+ priv->mode = PWM_MODE_MAX;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /*
+ * When switching to manual mode, set fan speed to
+ * current RPM values to ensure a smooth transition.
+ */
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ priv->pwm = rpm_to_pwm(rpm / 100, priv);
+ priv->mode = PWM_MODE_MANUAL;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_AUTO:
+ priv->mode = PWM_MODE_AUTO;
+ return hp_wmi_apply_fan_settings(priv);
default:
- /* we don't support manual fan speed control */
return -EINVAL;
}
default:
@@ -2196,7 +2480,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
NULL
};
@@ -2211,12 +2495,70 @@ static const struct hwmon_chip_info chip_info = {
.info = info,
};
+static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work)
+{
+ struct delayed_work *dwork;
+ struct hp_wmi_hwmon_priv *priv;
+
+ dwork = to_delayed_work(work);
+ priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork);
+ /*
+ * Re-apply the current hwmon context settings.
+ * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling.
+ */
+ hp_wmi_apply_fan_settings(priv);
+}
+
+static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ u8 fan_data[128] = { 0 };
+ struct victus_s_fan_table *fan_table;
+ u8 min_rpm, max_rpm, gpu_delta;
+ int ret;
+
+ /* Default behaviour on hwmon init is automatic mode */
+ priv->mode = PWM_MODE_AUTO;
+
+ /* Bypass all non-Victus S devices */
+ if (!is_victus_s_thermal_profile())
+ return 0;
+
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
+ HPWMI_GM, &fan_data, 4, sizeof(fan_data));
+ if (ret)
+ return ret;
+
+ fan_table = (struct victus_s_fan_table *)fan_data;
+ if (fan_table->header.num_entries == 0 ||
+ sizeof(struct victus_s_fan_table_header) +
+ sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data))
+ return -EINVAL;
+
+ min_rpm = fan_table->entries[0].cpu_rpm;
+ max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm;
+ gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
+ priv->min_rpm = min_rpm;
+ priv->max_rpm = max_rpm;
+ priv->gpu_delta = gpu_delta;
+
+ return 0;
+}
+
static int hp_wmi_hwmon_init(void)
{
struct device *dev = &hp_wmi_platform_dev->dev;
+ struct hp_wmi_hwmon_priv *priv;
struct device *hwmon;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
- hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
+ ret = hp_wmi_setup_fan_settings(priv);
+ if (ret)
+ return ret;
+ hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
&chip_info, NULL);
if (IS_ERR(hwmon)) {
@@ -2224,9 +2566,37 @@ static int hp_wmi_hwmon_init(void)
return PTR_ERR(hwmon);
}
+ INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
+ platform_set_drvdata(hp_wmi_platform_dev, priv);
+ hp_wmi_apply_fan_settings(priv);
+
return 0;
}
+static void __init setup_active_thermal_profile_params(void)
+{
+ const struct dmi_system_id *id;
+
+ /*
+ * Currently only victus_s devices use the
+ * active_thermal_profile_params
+ */
+ id = dmi_first_match(victus_s_thermal_profile_boards);
+ if (id) {
+ /*
+ * Marking this boolean is required to ensure that
+ * is_victus_s_thermal_profile() behaves like a valid
+ * wrapper.
+ */
+ is_victus_s_board = true;
+ active_thermal_profile_params = id->driver_data;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
+ dmi_get_system_info(DMI_BOARD_NAME));
+ }
+ }
+}
+
static int __init hp_wmi_init(void)
{
int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
@@ -2254,6 +2624,11 @@ static int __init hp_wmi_init(void)
goto err_destroy_input;
}
+ /*
+ * Setup active board's thermal profile parameters before
+ * starting platform driver probe.
+ */
+ setup_active_thermal_profile_params();
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
if (err)
goto err_unregister_device;
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 7d7ae8a40b0e..02b303418d18 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -776,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset,
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
- const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
- u32 offset = pmc->map->lpm_residency_offset;
- int mode;
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ int lpm_adj_x2;
+ struct pmc *pmc;
+ u32 offset;
+ u8 mode;
- seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
+ offset = pmc->map->lpm_residency_offset;
- pmc_for_each_mode(mode, pmcdev) {
- seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
- adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency");
+ pmc_for_each_mode(mode, pmc) {
+ seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode],
+ adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ }
}
return 0;
@@ -838,10 +848,11 @@ static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index,
enum header_type type)
{
struct pmc_dev *pmcdev = s->private;
- int mode;
+ struct pmc *pmc = pmcdev->pmcs[pmc_index];
+ u8 mode;
seq_printf(s, "%40s |", "Element");
- pmc_for_each_mode(mode, pmcdev)
+ pmc_for_each_mode(mode, pmc)
seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
if (type == HEADER_STATUS) {
@@ -880,14 +891,14 @@ static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused)
const struct pmc_bit_map *map;
for (map = maps[r_idx]; map->name; map++) {
- int mode;
+ u8 mode;
if (!map->blk)
continue;
counter = pmc_core_reg_read(pmc, offset);
seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
bool required = *lpm_req_regs & BIT(mode);
seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -953,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
u32 lpm_status;
u32 lpm_status_live;
const struct pmc_bit_map *map;
- int mode, i, len = 32;
+ int i, len = 32;
+ u8 mode;
/*
* Capture the requirements and create a mask so that we only
* show an element if it's required for at least one of the
* enabled low power modes
*/
- pmc_for_each_mode(mode, pmcdev)
+ pmc_for_each_mode(mode, pmc)
req_mask |= lpm_req_regs[mp + (mode * num_maps)];
/* Get the last latched status for this map */
@@ -986,7 +998,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name);
/* Loop over the enabled states and display if required */
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
bool required = lpm_req_regs[mp + (mode * num_maps)] &
bit_mask;
seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -1065,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool c10;
u32 reg;
- int mode;
+ u8 mode;
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
if (reg & LPM_STS_LATCH_MODE) {
@@ -1076,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
c10 = true;
}
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
if ((BIT(mode) & reg) && !c10)
seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
else
@@ -1097,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool clear = false, c10 = false;
unsigned char buf[8];
- int m, mode;
+ int mode;
u32 reg;
+ u8 m;
if (count > sizeof(buf) - 1)
return -EINVAL;
@@ -1115,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
mode = sysfs_match_string(pmc_lpm_modes, buf);
/* Check string matches enabled mode */
- pmc_for_each_mode(m, pmcdev)
+ pmc_for_each_mode(m, pmc)
if (mode == m)
break;
@@ -1211,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
return true;
}
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc)
{
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
u8 mode_order[LPM_MAX_NUM_MODES];
u32 lpm_pri;
u32 lpm_en;
+ u8 mode;
unsigned int i;
- int mode, p;
+ int p;
/* Use LPM Maps to indicate support for substates */
if (!pmc->map->lpm_num_maps)
@@ -1230,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
* Lower byte is enough to cover the number of lpm modes for all
* platforms and hence mask the upper 3 bytes.
*/
- pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
+ pmc->num_lpm_modes = hweight32(lpm_en & 0xFF);
/* Read 32 bit LPM_PRI register */
lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
-
/*
* If lpm_pri value passes verification, then override the default
* modes here. Otherwise stick with the default.
@@ -1254,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
*/
i = 0;
for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
- int mode = pri_order[p];
+ u8 mode = pri_order[p];
if (!(BIT(mode) & lpm_en))
continue;
- pmcdev->lpm_en_modes[i++] = mode;
+ pmc->lpm_en_modes[i++] = mode;
+ }
+}
+
+static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+{
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ pmc_core_pmc_get_low_power_modes(pmcdev, pmc);
}
}
@@ -1490,8 +1517,8 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
{
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
- int ret, mode;
- int lpm_size;
+ int ret, lpm_size;
+ u8 mode;
lpm_indices = pmc->map->lpm_reg_index;
num_maps = pmc->map->lpm_num_maps;
@@ -1504,7 +1531,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
return -ENOMEM;
mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
int m;
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 272fb4f57f34..118c8740ad3a 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -423,6 +423,8 @@ struct pmc_info {
* specific attributes
* @lpm_req_regs: List of substate requirements
* @ltr_ign: Holds LTR ignore data while suspended
+ * @num_lpm_modes: Count of enabled modes
+ * @lpm_en_modes: Array of enabled modes from lowest to highest priority
*
* pmc contains info about one power management controller device.
*/
@@ -432,6 +434,8 @@ struct pmc {
const struct pmc_reg_map *map;
u32 *lpm_req_regs;
u32 ltr_ign;
+ u8 num_lpm_modes;
+ u8 lpm_en_modes[LPM_MAX_NUM_MODES];
};
/**
@@ -446,8 +450,6 @@ struct pmc {
* @pkgc_res_cnt: Array of PKGC residency counters
* @num_of_pkgc: Number of PKGC
* @s0ix_counter: S0ix residency (step adjusted)
- * @num_lpm_modes: Count of enabled modes
- * @lpm_en_modes: Array of enabled modes from lowest to highest priority
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
*
@@ -462,8 +464,6 @@ struct pmc_dev {
struct mutex lock; /* generic mutex lock for PMC Core */
u64 s0ix_counter;
- int num_lpm_modes;
- int lpm_en_modes[LPM_MAX_NUM_MODES];
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
@@ -535,7 +535,6 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
int pmc_core_resume_common(struct pmc_dev *pmcdev);
int get_primary_reg_base(struct pmc *pmc);
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev);
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
void pmc_core_set_device_d3(unsigned int device);
@@ -563,10 +562,10 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
extern const struct file_operations pmc_core_substate_req_regs_fops;
extern const struct file_operations pmc_core_substate_blk_req_fops;
-#define pmc_for_each_mode(mode, pmcdev) \
+#define pmc_for_each_mode(mode, pmc) \
for (unsigned int __i = 0, __cond; \
- __cond = __i < (pmcdev)->num_lpm_modes, \
- __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \
+ __cond = __i < (pmc)->num_lpm_modes, \
+ __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \
__cond; \
__i++)
diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
index 7c3023d5d91d..be3c8d9e4fff 100644
--- a/drivers/platform/x86/intel/pmt/class.c
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -140,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "0x%x\n", entry->guid);
+ return sysfs_emit(buf, "0x%x\n", entry->guid);
}
static DEVICE_ATTR_RO(guid);
@@ -149,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%zu\n", entry->size);
+ return sysfs_emit(buf, "%zu\n", entry->size);
}
static DEVICE_ATTR_RO(size);
@@ -158,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
+ return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
}
static DEVICE_ATTR_RO(offset);
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
index 34bff2f65a83..9c078c8acb50 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -612,6 +612,9 @@ static long isst_if_core_power_state(void __user *argp)
return -EINVAL;
if (core_power.get_set) {
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
_write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
@@ -656,7 +659,7 @@ static long isst_if_clos_param(void __user *argp)
return -EINVAL;
if (clos_param.get_set) {
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
@@ -748,7 +751,8 @@ static long isst_if_clos_assoc(void __user *argp)
power_domain_info = &sst_inst->power_domain_info[part][punit_id];
- if (assoc_cmds.get_set && power_domain_info->write_blocked)
+ if (assoc_cmds.get_set && (power_domain_info->write_blocked ||
+ !capable(CAP_SYS_ADMIN)))
return -EPERM;
offset = SST_CLOS_ASSOC_0_OFFSET +
@@ -925,7 +929,7 @@ static int isst_if_set_perf_level(void __user *argp)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level)))
@@ -985,7 +989,7 @@ static int isst_if_set_perf_feature(void __user *argp)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
@@ -1717,58 +1721,87 @@ void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST");
+#define SST_PP_CAP_CP_ENABLE BIT(0)
+#define SST_PP_CAP_PP_ENABLE BIT(1)
+
void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
+
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_suspend;
- memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_suspend:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset +
- SST_PP_CONTROL_OFFSET);
+ pd_info->saved_pp_control = readq(pd_info->sst_base +
+ pd_info->sst_header.pp_offset +
+ SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST");
void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
+
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_resume;
- memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_resume:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
+ pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST");
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
index 65897fae17df..7070c94324e0 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
@@ -26,21 +26,21 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr,
{
struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
- return sprintf(buf, "%u\n", data->domain_id);
+ return sysfs_emit(buf, "%u\n", data->domain_id);
}
static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
- return sprintf(buf, "%u\n", data->cluster_id);
+ return sysfs_emit(buf, "%u\n", data->cluster_id);
}
static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
- return sprintf(buf, "%u\n", data->package_id);
+ return sysfs_emit(buf, "%u\n", data->package_id);
}
#define MAX_UNCORE_AGENT_TYPES 4
@@ -77,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index
if (ret)
return ret;
- return sprintf(buf, "%u\n", value);
+ return sysfs_emit(buf, "%u\n", value);
}
static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count,
@@ -269,9 +269,10 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu)
goto uncore_unlock;
data->instance_id = ret;
- sprintf(data->name, "uncore%02d", ret);
+ scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
} else {
- sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
+ scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
+ data->package_id, data->die_id);
}
uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
index 75c82c08117f..3716ccaaed6a 100644
--- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c
+++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
@@ -14,7 +14,6 @@
* https://slimbootloader.github.io/security/firmware-update.html
*/
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
@@ -25,41 +24,35 @@
static int get_fwu_request(struct device *dev, u32 *out)
{
- union acpi_object *obj;
+ struct wmi_buffer buffer;
+ __le32 *result;
+ int ret;
- obj = wmidev_block_query(to_wmi_device(dev), 0);
- if (!obj)
- return -ENODEV;
+ ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer);
+ if (ret < 0)
+ return ret;
- if (obj->type != ACPI_TYPE_INTEGER) {
- dev_warn(dev, "wmidev_block_query returned invalid value\n");
- kfree(obj);
- return -EINVAL;
+ if (buffer.length < sizeof(*result)) {
+ kfree(buffer.data);
+ return -ENODATA;
}
- *out = obj->integer.value;
- kfree(obj);
+ result = buffer.data;
+ *out = le32_to_cpu(*result);
+ kfree(result);
return 0;
}
static int set_fwu_request(struct device *dev, u32 in)
{
- struct acpi_buffer input;
- acpi_status status;
- u32 value;
-
- value = in;
- input.length = sizeof(u32);
- input.pointer = &value;
-
- status = wmidev_block_set(to_wmi_device(dev), 0, &input);
- if (ACPI_FAILURE(status)) {
- dev_err(dev, "wmidev_block_set failed\n");
- return -ENODEV;
- }
+ __le32 value = cpu_to_le32(in);
+ struct wmi_buffer buffer = {
+ .length = sizeof(value),
+ .data = &value,
+ };
- return 0;
+ return wmidev_set_block(to_wmi_device(dev), 0, &buffer);
}
static ssize_t firmware_update_request_show(struct device *dev,
diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c
index 15e5763a20dd..47017f2d7597 100644
--- a/drivers/platform/x86/intel/wmi/thunderbolt.c
+++ b/drivers/platform/x86/intel/wmi/thunderbolt.c
@@ -7,7 +7,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/hex.h>
@@ -24,24 +23,21 @@ static ssize_t force_power_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- struct acpi_buffer input;
- acpi_status status;
+ struct wmi_buffer buffer;
+ int ret;
u8 mode;
- input.length = sizeof(u8);
- input.pointer = &mode;
+ buffer.length = sizeof(mode);
+ buffer.data = &mode;
+
mode = hex_to_bin(buf[0]);
- dev_dbg(dev, "force_power: storing %#x\n", mode);
- if (mode == 0 || mode == 1) {
- status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL);
- if (ACPI_FAILURE(status)) {
- dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
- return -ENODEV;
- }
- } else {
- dev_dbg(dev, "force_power: unsupported mode\n");
+ if (mode > 1)
return -EINVAL;
- }
+
+ ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL);
+ if (ret < 0)
+ return ret;
+
return count;
}
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index d22b774e0236..f885127b007f 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -233,7 +233,7 @@ config YT2_1380
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook.
-config LENOVO_WMI_DATA01
+config LENOVO_WMI_CAPDATA
tristate
depends on ACPI_WMI
@@ -263,8 +263,9 @@ config LENOVO_WMI_GAMEZONE
config LENOVO_WMI_TUNING
tristate "Lenovo Other Mode WMI Driver"
depends on ACPI_WMI
+ select HWMON
select FW_ATTR_CLASS
- select LENOVO_WMI_DATA01
+ select LENOVO_WMI_CAPDATA
select LENOVO_WMI_EVENTS
select LENOVO_WMI_HELPERS
help
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e3a214..91a9370f11b3 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
-lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o
+lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o
lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 7d5f7a2f6564..06ac002a1ebc 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -219,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
static bool allow_v4_dytc;
module_param(allow_v4_dytc, bool, 0444);
MODULE_PARM_DESC(allow_v4_dytc,
- "Enable DYTC version 4 platform-profile support. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool hw_rfkill_switch;
module_param(hw_rfkill_switch, bool, 0444);
MODULE_PARM_DESC(hw_rfkill_switch,
- "Enable rfkill support for laptops with a hw on/off wifi switch/slider. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable rfkill support for laptops with a hw on/off wifi switch/slider. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool set_fn_lock_led;
module_param(set_fn_lock_led, bool, 0444);
MODULE_PARM_DESC(set_fn_lock_led,
- "Enable driver based updates of the fn-lock LED on fn-lock changes. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable driver based updates of the fn-lock LED on fn-lock changes. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool ctrl_ps2_aux_port;
module_param(ctrl_ps2_aux_port, bool, 0444);
MODULE_PARM_DESC(ctrl_ps2_aux_port,
- "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool touchpad_ctrl_via_ec;
module_param(touchpad_ctrl_via_ec, bool, 0444);
MODULE_PARM_DESC(touchpad_ctrl_via_ec,
- "Enable registering a 'touchpad' sysfs-attribute which can be used to manually "
- "tell the EC to enable/disable the touchpad. This may not work on all models.");
+ "Enable registering a 'touchpad' sysfs-attribute which can be used to manually tell the EC to enable/disable the touchpad. This may not work on all models.");
static bool ymc_ec_trigger __read_mostly;
module_param(ymc_ec_trigger, bool, 0444);
MODULE_PARM_DESC(ymc_ec_trigger,
- "Enable EC triggering work-around to force emitting tablet mode events. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable EC triggering work-around to force emitting tablet mode events. If you need this please report this to: platform-driver-x86@vger.kernel.org");
/*
* shared data
@@ -1446,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv)
if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
return;
- for_each_set_bit (bit, &value, 16) {
+ for_each_set_bit(bit, &value, 16) {
switch (bit) {
case 6: /* Z570 */
case 0: /* Z580 */
@@ -1706,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;
- if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
priv->kbd_bl.led.max_brightness = 2;
- } else {
+ else
priv->kbd_bl.led.max_brightness = 1;
- }
brightness = ideapad_kbd_bl_brightness_get(priv);
if (brightness < 0)
@@ -1752,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led
}
static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
@@ -1928,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
vpc1 = (vpc2 << 8) | vpc1;
- for_each_set_bit (bit, &vpc1, 16) {
+ for_each_set_bit(bit, &vpc1, 16) {
switch (bit) {
case 13:
case 11:
@@ -2142,14 +2135,14 @@ static const enum power_supply_property ideapad_power_supply_props[] = {
}
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
- (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
);
DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
- (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
- BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
);
static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index cc19fe520ea9..6b0e4b4c485e 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -36,6 +36,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -11080,6 +11081,206 @@ static const struct attribute_group auxmac_attr_group = {
.attrs = auxmac_attributes,
};
+/*************************************************************************
+ * HWDD subdriver, for the Lenovo Hardware Damage Detection feature.
+ */
+
+#define HWDD_GET_DMG_USBC 0x80000001
+#define HWDD_GET_CAP 0
+#define HWDD_NOT_SUPPORTED BIT(31)
+#define HWDD_SUPPORT_USBC BIT(0)
+
+#define PORT_STATUS GENMASK(7, 4)
+#define LID_STATUS GENMASK(11, 8)
+#define BASE_STATUS GENMASK(15, 12)
+#define POS_STATUS GENMASK(3, 2)
+#define PANEL_STATUS GENMASK(1, 0)
+
+#define PORT_DETAIL_OFFSET 16
+
+#define PANEL_TOP 0
+#define PANEL_BASE 1
+#define PANEL_LEFT 2
+#define PANEL_RIGHT 3
+
+#define POS_LEFT 0
+#define POS_CENTER 1
+#define POS_RIGHT 2
+
+#define NUM_PORTS 4
+
+static bool hwdd_support_available;
+static bool ucdd_supported;
+
+static int hwdd_command(int command, int *output)
+{
+ acpi_handle hwdd_handle;
+
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle)))
+ return -ENODEV;
+
+ if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command))
+ return -EIO;
+
+ return 0;
+}
+
+static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status)
+{
+ unsigned char lid_status, base_status, port_status;
+ unsigned char loc_status, pos_status, panel_status;
+ bool damage_detected = false;
+ int i;
+
+ port_status = FIELD_GET(PORT_STATUS, dmg_status);
+ lid_status = FIELD_GET(LID_STATUS, dmg_status);
+ base_status = FIELD_GET(BASE_STATUS, dmg_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(dmg_status & BIT(i)) || !(port_status & BIT(i)))
+ continue;
+
+ *count += sysfs_emit_at(buf, *count, "%s: ", type);
+ loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF;
+ pos_status = FIELD_GET(POS_STATUS, loc_status);
+ panel_status = FIELD_GET(PANEL_STATUS, loc_status);
+
+ if (lid_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Lid, ");
+ if (base_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Base, ");
+
+ switch (pos_status) {
+ case PANEL_TOP:
+ *count += sysfs_emit_at(buf, *count, "Top, ");
+ break;
+ case PANEL_BASE:
+ *count += sysfs_emit_at(buf, *count, "Bottom, ");
+ break;
+ case PANEL_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left, ");
+ break;
+ case PANEL_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right, ");
+ break;
+ default:
+ pr_err("Unexpected value %d in switch statement\n", pos_status);
+ }
+
+ switch (panel_status) {
+ case POS_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left port\n");
+ break;
+ case POS_CENTER:
+ *count += sysfs_emit_at(buf, *count, "Center port\n");
+ break;
+ case POS_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right port\n");
+ break;
+ default:
+ *count += sysfs_emit_at(buf, *count, "Undefined\n");
+ break;
+ }
+ damage_detected = true;
+ }
+ return damage_detected;
+}
+
+/* sysfs type-c damage detection detail */
+static ssize_t hwdd_detail_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status;
+ int err, count = 0;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ if (!display_damage(buf, &count, "Type-C", damage_status))
+ count += sysfs_emit_at(buf, count, "No damage detected\n");
+
+ return count;
+}
+
+/* sysfs type-c damage detection capability */
+static ssize_t hwdd_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status, port_status;
+ int err, i;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ port_status = FIELD_GET(PORT_STATUS, damage_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(damage_status & BIT(i)))
+ continue;
+ if (port_status & BIT(i))
+ return sysfs_emit(buf, "1\n");
+ }
+
+ return sysfs_emit(buf, "0\n");
+}
+static DEVICE_ATTR_RO(hwdd_status);
+static DEVICE_ATTR_RO(hwdd_detail);
+
+static struct attribute *hwdd_attributes[] = {
+ &dev_attr_hwdd_status.attr,
+ &dev_attr_hwdd_detail.attr,
+ NULL
+};
+
+static umode_t hwdd_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return hwdd_support_available ? attr->mode : 0;
+}
+
+static const struct attribute_group hwdd_attr_group = {
+ .is_visible = hwdd_attr_is_visible,
+ .attrs = hwdd_attributes,
+};
+
+static int tpacpi_hwdd_init(struct ibm_init_struct *iibm)
+{
+ int err, output;
+
+ /* Below command checks the HWDD damage capability */
+ err = hwdd_command(HWDD_GET_CAP, &output);
+ if (err)
+ return err;
+
+ if (!(output & HWDD_NOT_SUPPORTED))
+ return -ENODEV;
+
+ hwdd_support_available = true;
+
+ /*
+ * BIT(0) is assigned to check capability of damage detection is
+ * supported for USB Type-C port or not.
+ */
+ if (output & HWDD_SUPPORT_USBC)
+ ucdd_supported = true;
+
+ return err;
+}
+
+static struct ibm_struct hwdd_driver_data = {
+ .name = "hwdd",
+};
+
/* --------------------------------------------------------------------- */
static struct attribute *tpacpi_driver_attributes[] = {
@@ -11139,6 +11340,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&kbdlang_attr_group,
&dprc_attr_group,
&auxmac_attr_group,
+ &hwdd_attr_group,
NULL,
};
@@ -11752,6 +11954,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = auxmac_init,
.data = &auxmac_data,
},
+ {
+ .init = tpacpi_hwdd_init,
+ .data = &hwdd_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
new file mode 100644
index 000000000000..ee1fb02d8e31
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data WMI Data Block driver.
+ *
+ * Lenovo Capability Data provides information on tunable attributes used by
+ * the "Other Mode" WMI interface.
+ *
+ * Capability Data 00 includes if the attribute is supported by the hardware,
+ * and the default_value. All attributes are independent of thermal modes.
+ *
+ * Capability Data 01 includes if the attribute is supported by the hardware,
+ * and the default_value, max_value, min_value, and step increment. Each
+ * attribute has multiple pages, one for each of the thermal modes managed by
+ * the Gamezone interface.
+ *
+ * Fan Test Data includes the max/min fan speed RPM for each fan. This is
+ * reference data for self-test. If the fan is in good condition, it is capable
+ * to spin faster than max RPM or slower than min RPM.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - Unified implementation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata.h"
+
+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
+
+#define ACPI_AC_CLASS "ac_adapter"
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+#define LWMI_FEATURE_ID_FAN_TEST 0x05
+
+#define LWMI_ATTR_ID_FAN_TEST \
+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
+
+enum lwmi_cd_type {
+ LENOVO_CAPABILITY_DATA_00,
+ LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
+ CD_TYPE_NONE = -1,
+};
+
+#define LWMI_CD_TABLE_ITEM(_type) \
+ [_type] = { \
+ .name = #_type, \
+ .type = _type, \
+ }
+
+static const struct lwmi_cd_info {
+ const char *name;
+ enum lwmi_cd_type type;
+} lwmi_cd_table[] = {
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
+ LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
+};
+
+struct lwmi_cd_priv {
+ struct notifier_block acpi_nb; /* ACPI events */
+ struct wmi_device *wdev;
+ struct cd_list *list;
+
+ /*
+ * A capdata device may be a component master of another capdata device.
+ * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
+ * |- master |- component
+ * |- sub-master
+ * |- sub-component
+ */
+ struct lwmi_cd_sub_master_priv {
+ struct device *master_dev;
+ cd_list_cb_t master_cb;
+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
+ bool registered; /* Has the sub-master been registered? */
+ } *sub_master;
+};
+
+struct cd_list {
+ struct mutex list_mutex; /* list R/W mutex */
+ enum lwmi_cd_type type;
+ u8 count;
+
+ union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
+ DECLARE_FLEX_ARRAY(struct capdata01, cd01);
+ DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
+ };
+};
+
+static struct wmi_driver lwmi_cd_driver;
+
+/**
+ * lwmi_cd_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data parent device.
+ * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
+ *
+ * Return: int.
+ */
+static int lwmi_cd_match(struct device *dev, void *type)
+{
+ struct lwmi_cd_priv *priv;
+
+ if (dev->driver != &lwmi_cd_driver.driver)
+ return false;
+
+ priv = dev_get_drvdata(dev);
+ return priv->list->type == *(enum lwmi_cd_type *)type;
+}
+
+/**
+ * lwmi_cd_match_add_all() - Add all match rule for the master driver.
+ * @master: Pointer to the master device.
+ * @matchptr: Pointer to the returned component_match pointer.
+ *
+ * Adds all component matches to the list stored in @matchptr for the @master
+ * device. @matchptr must be initialized to NULL.
+ */
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
+{
+ int i;
+
+ if (WARN_ON(*matchptr))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ /* Skip sub-components. */
+ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
+ continue;
+
+ component_match_add(master, matchptr, lwmi_cd_match,
+ (void *)&lwmi_cd_table[i].type);
+ if (IS_ERR(*matchptr))
+ return;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
+ * @priv: Pointer to the capability data private data.
+ *
+ * Call the master callback and pass the sub-component list to it if the
+ * dependency chain (master <-> sub-master <-> sub-component) is complete.
+ */
+static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
+{
+ struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
+
+ /*
+ * Call the callback only if the dependency chain is ready:
+ * - Binding between master and sub-master: fills master_dev and master_cb
+ * - Binding between sub-master and sub-component: fills sub_component_list
+ *
+ * If a binding has been unbound before the other binding is bound, the
+ * corresponding members filled by the former are guaranteed to be cleared.
+ *
+ * This function is only called in bind callbacks, and the component
+ * framework guarantees bind/unbind callbacks may never execute
+ * simultaneously, which implies that it's impossible to have a race
+ * condition.
+ *
+ * Hence, this check is sufficient to ensure that the callback is called
+ * at most once and with the correct state, without relying on a specific
+ * sequence of binding establishment.
+ */
+ if (!sub_component_list ||
+ !priv->sub_master->master_dev ||
+ !priv->sub_master->master_cb)
+ return;
+
+ if (PTR_ERR(sub_component_list) == -ENODEV)
+ sub_component_list = NULL;
+ else if (WARN_ON(IS_ERR(sub_component_list)))
+ return;
+
+ priv->sub_master->master_cb(priv->sub_master->master_dev,
+ sub_component_list);
+
+ /*
+ * Userspace may unbind a device from its driver and bind it again
+ * through sysfs. Let's call this operation "reprobe" to distinguish it
+ * from component "rebind".
+ *
+ * When reprobing capdata00/01 or the master device, the master device
+ * is unbound from us with appropriate cleanup before we bind to it and
+ * call master_cb. Everything is fine in this case.
+ *
+ * When reprobing capdata_fan, the master device has never been unbound
+ * from us (hence no cleanup is done)[1], but we call master_cb the
+ * second time. To solve this issue, we clear master_cb and master_dev
+ * so we won't call master_cb twice while a binding is still complete.
+ *
+ * Note that we can't clear sub_component_list, otherwise reprobing
+ * capdata01 or the master device causes master_cb to be never called
+ * after we rebind to the master device.
+ *
+ * [1]: The master device does not need capdata_fan in run time, so
+ * losing capdata_fan will not break the binding to the master device.
+ */
+ priv->sub_master->master_cb = NULL;
+ priv->sub_master->master_dev = NULL;
+}
+
+/**
+ * lwmi_cd_component_bind() - Bind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: lwmi_cd_binder object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
+ * list. This is used to call lwmi_cd*_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * If cd_dev is a sub-master, try to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_component_bind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+ struct lwmi_cd_binder *binder = data;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ binder->cd00_list = priv->list;
+
+ priv->sub_master->master_dev = om_dev;
+ priv->sub_master->master_cb = binder->cd_fan_list_cb;
+ lwmi_cd_call_master_cb(priv);
+
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ binder->cd01_list = priv->list;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_component_unbind() - Unbind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: Unused.
+ *
+ * If cd_dev is a sub-master, clear the collected data from the master device to
+ * prevent the binding establishment between the sub-master and the sub-
+ * component (if it's about to happen) from calling the master callback.
+ */
+static void lwmi_cd_component_unbind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ priv->sub_master->master_dev = NULL;
+ priv->sub_master->master_cb = NULL;
+ return;
+ default:
+ return;
+ }
+}
+
+static const struct component_ops lwmi_cd_component_ops = {
+ .bind = lwmi_cd_component_bind,
+ .unbind = lwmi_cd_component_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device.
+ *
+ * Call component_bind_all to bind the sub-component device to the sub-master
+ * device. On success, collect the pointer to the sub-component list and try
+ * to call the master callback.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_bind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+ struct cd_list *sub_component_list;
+ int ret;
+
+ ret = component_bind_all(dev, &sub_component_list);
+ if (ret)
+ return ret;
+
+ priv->sub_master->sub_component_list = sub_component_list;
+ lwmi_cd_call_master_cb(priv);
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device
+ *
+ * Clear the collected pointer to the sub-component list to prevent the binding
+ * establishment between the sub-master and the sub-component (if it's about to
+ * happen) from calling the master callback. Then, call component_unbind_all to
+ * unbind the sub-component device from the sub-master device.
+ */
+static void lwmi_cd_sub_master_unbind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+
+ priv->sub_master->sub_component_list = NULL;
+
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_cd_sub_master_ops = {
+ .bind = lwmi_cd_sub_master_bind,
+ .unbind = lwmi_cd_sub_master_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
+ * @priv: Pointer to the sub-master capdata device private data.
+ * @sub_component_type: Type of the sub-component.
+ *
+ * Match the sub-component type and register the current capdata device as a
+ * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
+ * component as non-existent without registering sub-master.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
+ enum lwmi_cd_type sub_component_type)
+{
+ struct component_match *master_match = NULL;
+ int ret;
+
+ priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
+ if (!priv->sub_master)
+ return -ENOMEM;
+
+ if (sub_component_type == CD_TYPE_NONE) {
+ /* The master callback will be called with NULL on bind. */
+ priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
+ priv->sub_master->registered = false;
+ return 0;
+ }
+
+ /*
+ * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
+ * data cannot be used here. Steal one from lwmi_cd_table.
+ */
+ component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
+ (void *)&lwmi_cd_table[sub_component_type].type);
+ if (IS_ERR(master_match))
+ return PTR_ERR(master_match);
+
+ ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
+ master_match);
+ if (ret)
+ return ret;
+
+ priv->sub_master->registered = true;
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
+ * @priv: Pointer to the sub-master capdata device private data.
+ */
+static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
+{
+ if (!priv->sub_master->registered)
+ return;
+
+ component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
+ priv->sub_master->registered = false;
+}
+
+/**
+ * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
+ * @sc_dev: Pointer to the sub-component capdata parent device.
+ * @sm_dev: Pointer to the sub-master capdata parent device.
+ * @data: Pointer used to return the capability data list pointer.
+ *
+ * On sub-master's bind, provide a pointer to the local capdata list.
+ * This is used by the sub-master to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_sub_component_bind(struct device *sc_dev,
+ struct device *sm_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
+ struct cd_list **listp = data;
+
+ *listp = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd_sub_component_ops = {
+ .bind = lwmi_cd_sub_component_bind,
+};
+
+/*
+ * lwmi_cd*_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata* struct to return the data.
+ *
+ * Retrieves the capability data struct pointer for the given
+ * attribute.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
+ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
+ { \
+ u8 idx; \
+ \
+ if (WARN_ON(list->type != _cd_type)) \
+ return -EINVAL; \
+ \
+ guard(mutex)(&list->list_mutex); \
+ for (idx = 0; idx < list->count; idx++) { \
+ if (list->_cdxx[idx].id != attribute_id) \
+ continue; \
+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
+ return 0; \
+ } \
+ return -EINVAL; \
+ }
+
+DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+{
+ size_t size;
+ int idx;
+ void *p;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ p = &priv->list->cd00[0];
+ size = sizeof(priv->list->cd00[0]);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ p = &priv->list->cd01[0];
+ size = sizeof(priv->list->cd01[0]);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->list->list_mutex);
+ for (idx = 0; idx < priv->list->count; idx++, p += size) {
+ union acpi_object *ret_obj __free(kfree) = NULL;
+
+ ret_obj = wmidev_block_query(priv->wdev, idx);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER ||
+ ret_obj->buffer.length < size)
+ continue;
+
+ memcpy(p, ret_obj->buffer.pointer, size);
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
+ * @priv: lenovo-wmi-capdata driver data.
+ * @listptr: Pointer to returned cd_list pointer.
+ *
+ * Return: count of fans found, or an error.
+ */
+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
+{
+ struct cd_list *list;
+ size_t size;
+ u32 count;
+ int idx;
+
+ /* Emit unaligned access to u8 buffer with __packed. */
+ struct cd_fan_block {
+ u32 nr;
+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
+ } __packed * block;
+
+ union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type == ACPI_TYPE_BUFFER) {
+ block = (struct cd_fan_block *)ret_obj->buffer.pointer;
+ size = ret_obj->buffer.length;
+
+ count = size >= sizeof(*block) ? block->nr : 0;
+ if (size < struct_size(block, data, count * 3)) {
+ dev_warn(&priv->wdev->dev,
+ "incomplete fan test data block: %zu < %zu, ignoring\n",
+ size, struct_size(block, data, count * 3));
+ count = 0;
+ } else if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+ } else {
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in sub-master device being
+ * unbound. This behavior aligns with lwmi_cd_cache().
+ */
+ count = 0;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ /* Do not calculate array index using count, as it may be truncated. */
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = block->data[idx],
+ .max_rpm = block->data[idx + block->nr],
+ .min_rpm = block->data[idx + (2 * block->nr)],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
+/**
+ * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ struct cd_list *list;
+ size_t list_size;
+ int count, ret;
+
+ count = wmidev_instance_count(priv->wdev);
+
+ switch (type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ list_size = struct_size(list, cd00, count);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ list_size = struct_size(list, cd01, count);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ count = lwmi_cd_fan_list_alloc_cache(priv, &list);
+ if (count < 0)
+ return count;
+
+ goto got_list;
+ default:
+ return -EINVAL;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+got_list:
+ ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+ if (ret)
+ return ret;
+
+ list->type = type;
+ list->count = count;
+ priv->list = list;
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ int ret;
+
+ ret = lwmi_cd_alloc(priv, type);
+ if (ret)
+ return ret;
+
+ return lwmi_cd_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for cd01 notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct acpi_bus_event *event = data;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
+
+ switch (event->type) {
+ case ACPI_AC_NOTIFY_STATUS:
+ ret = lwmi_cd_cache(priv);
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
+ * @data: The ACPI event notifier_block to unregister.
+ */
+static void lwmi_cd01_unregister(void *data)
+{
+ struct notifier_block *acpi_nb = data;
+
+ unregister_acpi_notifier(acpi_nb);
+}
+
+static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
+{
+ const struct lwmi_cd_info *info = context;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (!info)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ ret = lwmi_cd_setup(priv, info->type);
+ if (ret)
+ goto out;
+
+ switch (info->type) {
+ case LENOVO_CAPABILITY_DATA_00: {
+ enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
+ struct capdata00 capdata00;
+
+ ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
+ if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
+ dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
+ sub_component_type = CD_TYPE_NONE;
+ }
+
+ /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
+ ret = lwmi_cd_sub_master_add(priv, sub_component_type);
+ if (ret)
+ goto out;
+
+ /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ if (ret)
+ lwmi_cd_sub_master_del(priv);
+
+ goto out;
+ }
+ case LENOVO_CAPABILITY_DATA_01:
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ goto out;
+ case LENOVO_FAN_TEST_DATA:
+ ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
+ goto out;
+ default:
+ return -EINVAL;
+ }
+out:
+ if (ret) {
+ dev_err(&wdev->dev, "failed to register %s: %d\n",
+ info->name, ret);
+ } else {
+ dev_dbg(&wdev->dev, "registered %s with %u items\n",
+ info->name, priv->list->count);
+ }
+ return ret;
+}
+
+static void lwmi_cd_remove(struct wmi_device *wdev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ lwmi_cd_sub_master_del(priv);
+ fallthrough;
+ case LENOVO_CAPABILITY_DATA_01:
+ component_del(&wdev->dev, &lwmi_cd_component_ops);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+
+#define LWMI_CD_WDEV_ID(_type) \
+ .guid_string = _type##_GUID, \
+ .context = &lwmi_cd_table[_type],
+
+static const struct wmi_device_id lwmi_cd_id_table[] = {
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
+ { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
+ {}
+};
+
+static struct wmi_driver lwmi_cd_driver = {
+ .driver = {
+ .name = "lenovo_wmi_capdata",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_cd_id_table,
+ .probe = lwmi_cd_probe,
+ .remove = lwmi_cd_remove,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_cd_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
new file mode 100644
index 000000000000..8c1df3efcc55
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA_H_
+#define _LENOVO_WMI_CAPDATA_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+#define LWMI_SUPP_VALID BIT(0)
+#define LWMI_SUPP_GET BIT(1)
+#define LWMI_SUPP_SET BIT(2)
+
+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+#define LWMI_DEVICE_ID_FAN 0x04
+
+struct component_match;
+struct device;
+struct cd_list;
+
+struct capdata00 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+};
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
+struct capdata_fan {
+ u32 id;
+ u32 min_rpm;
+ u32 max_rpm;
+};
+
+typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list);
+
+struct lwmi_cd_binder {
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+ /*
+ * May be called during or after the bind callback.
+ * Will be called with NULL if capdata_fan does not exist.
+ * The pointer is only valid in the callback; never keep it for later use!
+ */
+ cd_list_cb_t cd_fan_list_cb;
+};
+
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
+int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
+
+#endif /* !_LENOVO_WMI_CAPDATA_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c
deleted file mode 100644
index fc7e3454e71d..000000000000
--- a/drivers/platform/x86/lenovo/wmi-capdata01.c
+++ /dev/null
@@ -1,302 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Lenovo Capability Data 01 WMI Data Block driver.
- *
- * Lenovo Capability Data 01 provides information on tunable attributes used by
- * the "Other Mode" WMI interface. The data includes if the attribute is
- * supported by the hardware, the default_value, max_value, min_value, and step
- * increment. Each attribute has multiple pages, one for each of the thermal
- * modes managed by the Gamezone interface.
- *
- * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
- */
-
-#include <linux/acpi.h>
-#include <linux/cleanup.h>
-#include <linux/component.h>
-#include <linux/container_of.h>
-#include <linux/device.h>
-#include <linux/export.h>
-#include <linux/gfp_types.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/mutex_types.h>
-#include <linux/notifier.h>
-#include <linux/overflow.h>
-#include <linux/types.h>
-#include <linux/wmi.h>
-
-#include "wmi-capdata01.h"
-
-#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
-
-#define ACPI_AC_CLASS "ac_adapter"
-#define ACPI_AC_NOTIFY_STATUS 0x80
-
-struct lwmi_cd01_priv {
- struct notifier_block acpi_nb; /* ACPI events */
- struct wmi_device *wdev;
- struct cd01_list *list;
-};
-
-struct cd01_list {
- struct mutex list_mutex; /* list R/W mutex */
- u8 count;
- struct capdata01 data[];
-};
-
-/**
- * lwmi_cd01_component_bind() - Bind component to master device.
- * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
- * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
- * @data: capdata01_list object pointer used to return the capability data.
- *
- * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
- * list. This is used to call lwmi_cd01_get_data to look up attribute data
- * from the lenovo-wmi-other driver.
- *
- * Return: 0
- */
-static int lwmi_cd01_component_bind(struct device *cd01_dev,
- struct device *om_dev, void *data)
-{
- struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
- struct cd01_list **cd01_list = data;
-
- *cd01_list = priv->list;
-
- return 0;
-}
-
-static const struct component_ops lwmi_cd01_component_ops = {
- .bind = lwmi_cd01_component_bind,
-};
-
-/**
- * lwmi_cd01_get_data - Get the data of the specified attribute
- * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
- * @attribute_id: The capdata attribute ID to be found.
- * @output: Pointer to a capdata01 struct to return the data.
- *
- * Retrieves the capability data 01 struct pointer for the given
- * attribute for its specified thermal mode.
- *
- * Return: 0 on success, or -EINVAL.
- */
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
-{
- u8 idx;
-
- guard(mutex)(&list->list_mutex);
- for (idx = 0; idx < list->count; idx++) {
- if (list->data[idx].id != attribute_id)
- continue;
- memcpy(output, &list->data[idx], sizeof(list->data[idx]));
- return 0;
- }
-
- return -EINVAL;
-}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
-
-/**
- * lwmi_cd01_cache() - Cache all WMI data block information
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Loop through each WMI data block and cache the data.
- *
- * Return: 0 on success, or an error.
- */
-static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
-{
- int idx;
-
- guard(mutex)(&priv->list->list_mutex);
- for (idx = 0; idx < priv->list->count; idx++) {
- union acpi_object *ret_obj __free(kfree) = NULL;
-
- ret_obj = wmidev_block_query(priv->wdev, idx);
- if (!ret_obj)
- return -ENODEV;
-
- if (ret_obj->type != ACPI_TYPE_BUFFER ||
- ret_obj->buffer.length < sizeof(priv->list->data[idx]))
- continue;
-
- memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
- ret_obj->buffer.length);
- }
-
- return 0;
-}
-
-/**
- * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Allocate a cd01_list struct large enough to contain data from all WMI data
- * blocks provided by the interface.
- *
- * Return: 0 on success, or an error.
- */
-static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
-{
- struct cd01_list *list;
- size_t list_size;
- int count, ret;
-
- count = wmidev_instance_count(priv->wdev);
- list_size = struct_size(list, data, count);
-
- list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
- if (!list)
- return -ENOMEM;
-
- ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
- if (ret)
- return ret;
-
- list->count = count;
- priv->list = list;
-
- return 0;
-}
-
-/**
- * lwmi_cd01_setup() - Cache all WMI data block information
- * @priv: lenovo-wmi-capdata01 driver data.
- *
- * Allocate a cd01_list struct large enough to contain data from all WMI data
- * blocks provided by the interface. Then loop through each data block and
- * cache the data.
- *
- * Return: 0 on success, or an error code.
- */
-static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
-{
- int ret;
-
- ret = lwmi_cd01_alloc(priv);
- if (ret)
- return ret;
-
- return lwmi_cd01_cache(priv);
-}
-
-/**
- * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
- * block call chain.
- * @nb: The notifier_block registered to lenovo-wmi-events driver.
- * @action: Unused.
- * @data: The ACPI event.
- *
- * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
- * of a change.
- *
- * Return: notifier_block status.
- */
-static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
- void *data)
-{
- struct acpi_bus_event *event = data;
- struct lwmi_cd01_priv *priv;
- int ret;
-
- if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
- return NOTIFY_DONE;
-
- priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
-
- switch (event->type) {
- case ACPI_AC_NOTIFY_STATUS:
- ret = lwmi_cd01_cache(priv);
- if (ret)
- return NOTIFY_BAD;
-
- return NOTIFY_OK;
- default:
- return NOTIFY_DONE;
- }
-}
-
-/**
- * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
- * @data: The ACPI event notifier_block to unregister.
- */
-static void lwmi_cd01_unregister(void *data)
-{
- struct notifier_block *acpi_nb = data;
-
- unregister_acpi_notifier(acpi_nb);
-}
-
-static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
-
-{
- struct lwmi_cd01_priv *priv;
- int ret;
-
- priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->wdev = wdev;
- dev_set_drvdata(&wdev->dev, priv);
-
- ret = lwmi_cd01_setup(priv);
- if (ret)
- return ret;
-
- priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
-
- ret = register_acpi_notifier(&priv->acpi_nb);
- if (ret)
- return ret;
-
- ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
- if (ret)
- return ret;
-
- return component_add(&wdev->dev, &lwmi_cd01_component_ops);
-}
-
-static void lwmi_cd01_remove(struct wmi_device *wdev)
-{
- component_del(&wdev->dev, &lwmi_cd01_component_ops);
-}
-
-static const struct wmi_device_id lwmi_cd01_id_table[] = {
- { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
- {}
-};
-
-static struct wmi_driver lwmi_cd01_driver = {
- .driver = {
- .name = "lenovo_wmi_cd01",
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
- .id_table = lwmi_cd01_id_table,
- .probe = lwmi_cd01_probe,
- .remove = lwmi_cd01_remove,
- .no_singleton = true,
-};
-
-/**
- * lwmi_cd01_match() - Match rule for the master driver.
- * @dev: Pointer to the capability data 01 parent device.
- * @data: Unused void pointer for passing match criteria.
- *
- * Return: int.
- */
-int lwmi_cd01_match(struct device *dev, void *data)
-{
- return dev->driver == &lwmi_cd01_driver.driver;
-}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
-
-module_wmi_driver(lwmi_cd01_driver);
-
-MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
-MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
-MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata01.h
deleted file mode 100644
index bd06c5751f68..000000000000
--- a/drivers/platform/x86/lenovo/wmi-capdata01.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
-
-#ifndef _LENOVO_WMI_CAPDATA01_H_
-#define _LENOVO_WMI_CAPDATA01_H_
-
-#include <linux/types.h>
-
-struct device;
-struct cd01_list;
-
-struct capdata01 {
- u32 id;
- u32 supported;
- u32 default_value;
- u32 step;
- u32 min_value;
- u32 max_value;
-};
-
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
-int lwmi_cd01_match(struct device *dev, void *data);
-
-#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index f6fef6296251..7379defac500 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -21,6 +21,7 @@
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
+#include <linux/unaligned.h>
#include <linux/wmi.h>
#include "wmi-helpers.h"
@@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
if (!ret_obj)
return -ENODATA;
- if (ret_obj->type != ACPI_TYPE_INTEGER)
- return -ENXIO;
+ switch (ret_obj->type) {
+ /*
+ * The ACPI method may simply return a buffer when a u32
+ * is expected. This is valid on Windows as its WMI-ACPI
+ * driver converts everything to a common buffer.
+ */
+ case ACPI_TYPE_BUFFER:
+ if (ret_obj->buffer.length < sizeof(u32))
+ return -ENXIO;
- *retval = (u32)ret_obj->integer.value;
+ *retval = get_unaligned_le32(ret_obj->buffer.pointer);
+ return 0;
+ case ACPI_TYPE_INTEGER:
+ *retval = (u32)ret_obj->integer.value;
+ return 0;
+ default:
+ return -ENXIO;
+ }
}
return 0;
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 2a960b278f11..6040f45aa2b0 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -14,7 +14,16 @@
* These attributes typically don't fit anywhere else in the sysfs and are set
* in Windows using one of Lenovo's multiple user applications.
*
+ * Additionally, this driver also exports tunable fan speed RPM to HWMON.
+ * Min/max RPM are also provided for reference.
+ *
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - fw_attributes
+ * - binding to Capability Data 01
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - HWMON
+ * - binding to Capability Data 00 and Fan
*/
#include <linux/acpi.h>
@@ -25,16 +34,18 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_profile.h>
#include <linux/types.h>
#include <linux/wmi.h>
-#include "wmi-capdata01.h"
+#include "wmi-capdata.h"
#include "wmi-events.h"
#include "wmi-gamezone.h"
#include "wmi-helpers.h"
@@ -49,17 +60,26 @@
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
-#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
-#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
-#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
-#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+#define LWMI_FAN_ID_BASE 1
+#define LWMI_FAN_NR 4
+#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
+
+#define LWMI_ATTR_ID_FAN_RPM(x) \
+ (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
+
+#define LWMI_FAN_DIV 100
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
static DEFINE_IDA(lwmi_om_ida);
@@ -72,16 +92,459 @@ enum attribute_property {
SUPPORTED,
};
+struct lwmi_fan_info {
+ u32 supported;
+ u32 last_target;
+ long min_rpm;
+ long max_rpm;
+};
+
struct lwmi_om_priv {
struct component_master_ops *ops;
- struct cd01_list *cd01_list; /* only valid after capdata01 bind */
+
+ /* only valid after capdata bind */
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+
+ struct device *hwmon_dev;
struct device *fw_attr_dev;
struct kset *fw_attr_kset;
struct notifier_block nb;
struct wmi_device *wdev;
int ida_id;
+
+ struct lwmi_fan_info fan_info[LWMI_FAN_NR];
+
+ struct {
+ bool capdata00_collected : 1;
+ bool capdata_fan_collected : 1;
+ } fan_flags;
};
+/*
+ * Visibility of fan channels:
+ *
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | | default | +expose_all_fans | +relax_fan_constraint | +both |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | canonical | RW | RW | RW+relaxed | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | -capdata_fan[idx] | N | RO | N | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ *
+ * Note:
+ * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel.
+ * 2. -capdata_fan implies -capdata_fan[idx].
+ */
+static bool expose_all_fans;
+module_param(expose_all_fans, bool, 0444);
+MODULE_PARM_DESC(expose_all_fans,
+ "This option skips some capability checks and solely relies on per-channel ones "
+ "to expose fan attributes. Use with caution.");
+
+static bool relax_fan_constraint;
+module_param(relax_fan_constraint, bool, 0444);
+MODULE_PARM_DESC(relax_fan_constraint,
+ "Do not enforce fan RPM constraint (div/min/max) "
+ "and enables fan tuning when such data is missing. "
+ "Enabling this may results in HWMON attributes being out-of-sync, "
+ "and setting a too low RPM stops the fan. Use with caution.");
+
+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
+
+/**
+ * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
+ * @priv: Driver private data structure
+ * @channel: Fan channel index (0-based)
+ * @val: Pointer to value (input for set, output for get)
+ * @set: True to set value, false to get value
+ *
+ * Communicates with WMI interface to either retrieve current fan RPM
+ * or set target fan RPM.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
+{
+ struct wmi_method_args_32 args;
+ u32 method_id, retval;
+ int err;
+
+ method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
+ args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
+ args.arg1 = set ? *val : 0;
+
+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
+ (unsigned char *)&args, sizeof(args), &retval);
+ if (err)
+ return err;
+
+ if (!set) {
+ *val = retval;
+ return 0;
+ }
+
+ /*
+ * It seems that 0 means "no error" and 1 means "done". Apparently
+ * different firmware teams have different thoughts on indicating
+ * success, so we accepts both.
+ */
+ return (retval == 0 || retval == 1) ? 0 : -EIO;
+}
+
+/**
+ * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
+ * @drvdata: Driver private data
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ *
+ * Determines whether an HWMON attribute should be visible in sysfs
+ * based on hardware capabilities and current configuration.
+ *
+ * Return: permission mode, or 0 if invisible.
+ */
+static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
+ bool visible = false;
+
+ if (type == hwmon_fan) {
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
+ return 0;
+
+ switch (attr) {
+ case hwmon_fan_target:
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
+ return 0;
+
+ if (relax_fan_constraint ||
+ (priv->fan_info[channel].min_rpm >= 0 &&
+ priv->fan_info[channel].max_rpm >= 0))
+ return 0644;
+
+ /*
+ * Reaching here implies expose_all_fans is set.
+ * See lwmi_om_hwmon_add().
+ */
+ dev_warn_once(&priv->wdev->dev,
+ "fan tuning disabled due to missing RPM constraint\n");
+ return 0;
+ case hwmon_fan_div:
+ case hwmon_fan_input:
+ visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
+ break;
+ case hwmon_fan_min:
+ visible = priv->fan_info[channel].min_rpm >= 0;
+ break;
+ case hwmon_fan_max:
+ visible = priv->fan_info[channel].max_rpm >= 0;
+ break;
+ }
+ }
+
+ return visible ? 0444 : 0;
+}
+
+/**
+ * lwmi_om_hwmon_read() - Read HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Pointer to store value
+ *
+ * Reads current sensor values from hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 retval = 0;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ /*
+ * The EC has an internal RPM divisor (i.e., the raw register value is
+ * RPM / fanY_div). For fanY_input, the WMI method reads the register
+ * value and returns raw * fanY_div. For fanY_target, the WMI method
+ * divides the written value by fanY_div before writing it to the EC.
+ *
+ * As a result, reading fanY_input always returns a multiple of fanY_div,
+ * while writing to fanY_target loses the remainder.
+ */
+ case hwmon_fan_div:
+ *val = LWMI_FAN_DIV;
+ return 0;
+ case hwmon_fan_input:
+ err = lwmi_om_fan_get_set(priv, channel, &retval, false);
+ if (err)
+ return err;
+
+ *val = retval;
+ return 0;
+ case hwmon_fan_target:
+ *val = priv->fan_info[channel].last_target;
+ return 0;
+ case hwmon_fan_min:
+ *val = priv->fan_info[channel].min_rpm;
+ return 0;
+ case hwmon_fan_max:
+ *val = priv->fan_info[channel].max_rpm;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * lwmi_om_hwmon_write() - Write HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Value to write
+ *
+ * Writes configuration values to hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 raw, min_rpm, max_rpm;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_target:
+ if (relax_fan_constraint) {
+ min_rpm = 1;
+ max_rpm = U16_MAX;
+ } else {
+ min_rpm = priv->fan_info[channel].min_rpm;
+ max_rpm = priv->fan_info[channel].max_rpm;
+ }
+
+ /* 0 means "auto". */
+ if (val != 0 && (val < min_rpm || val > max_rpm))
+ return -EINVAL;
+
+ /*
+ * The effective fanY_target is always a multiple of fanY_div
+ * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read).
+ *
+ * Round down the written value to the nearest multiple of fanY_div
+ * to prevent mismatch between the effective value and last_target.
+ *
+ * For relax_fan_constraint, skip this conversion as setting a
+ * sub-fanY_div value is necessary to completely stop the fan on
+ * some devices.
+ */
+ if (!relax_fan_constraint)
+ raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
+
+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
+ if (err)
+ return err;
+
+ priv->fan_info[channel].last_target = raw;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
+ /* Must match LWMI_FAN_NR. */
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_ops lwmi_om_hwmon_ops = {
+ .is_visible = lwmi_om_hwmon_is_visible,
+ .read = lwmi_om_hwmon_read,
+ .write = lwmi_om_hwmon_write,
+};
+
+static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
+ .ops = &lwmi_om_hwmon_ops,
+ .info = lwmi_om_hwmon_info,
+};
+
+/**
+ * lwmi_om_hwmon_add() - Register HWMON device if all info is collected
+ * @priv: Driver private data
+ */
+static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
+{
+ int i, valid;
+
+ if (WARN_ON(priv->hwmon_dev))
+ return;
+
+ if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
+ dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
+ priv->fan_flags.capdata00_collected,
+ priv->fan_flags.capdata_fan_collected);
+ return;
+ }
+
+ if (expose_all_fans)
+ dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
+
+ if (relax_fan_constraint)
+ dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
+
+ valid = 0;
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
+ continue;
+
+ valid++;
+
+ if (!expose_all_fans &&
+ (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
+ dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
+ LWMI_FAN_ID(i));
+ priv->fan_info[i].supported = 0;
+ valid--;
+ }
+ }
+
+ if (valid == 0) {
+ dev_warn(&priv->wdev->dev,
+ "fan reporting/tuning is unsupported on this device\n");
+ return;
+ }
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
+ LWMI_OM_HWMON_NAME, priv,
+ &lwmi_om_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
+ PTR_ERR(priv->hwmon_dev));
+ priv->hwmon_dev = NULL;
+ return;
+ }
+
+ dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
+}
+
+/**
+ * lwmi_om_hwmon_remove() - Unregister HWMON device
+ * @priv: Driver private data
+ *
+ * Unregisters the HWMON device if applicable.
+ */
+static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
+{
+ if (!priv->hwmon_dev)
+ return;
+
+ hwmon_device_unregister(priv->hwmon_dev);
+ priv->hwmon_dev = NULL;
+}
+
+/**
+ * lwmi_om_fan_info_init() - Initialzie fan info
+ * @priv: Driver private data
+ *
+ * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be
+ * called in an arbitrary order. Hence, initializion must be done before.
+ */
+static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ priv->fan_info[i] = (struct lwmi_fan_info) {
+ .supported = 0,
+ /*
+ * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot.
+ *
+ * Note that S0ix (s2idle) preserves the RPM target, so we don't need
+ * suspend/resume callbacks. This behavior has not been tested on S3-
+ * capable devices, but I doubt if such devices even have this interface.
+ */
+ .last_target = 0,
+ .min_rpm = -ENODATA,
+ .max_rpm = -ENODATA,
+ };
+ }
+
+ priv->fan_flags.capdata00_collected = false;
+ priv->fan_flags.capdata_fan_collected = false;
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00
+ * @priv: Driver private data
+ */
+static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
+{
+ struct capdata00 capdata00;
+ int i, err;
+
+ dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
+ priv->fan_info[i].supported = err ? 0 : capdata00.supported;
+ }
+
+ priv->fan_flags.capdata00_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan
+ * @dev: Pointer to the lenovo-wmi-other device
+ * @cd_fan_list: Pointer to the capdata fan list
+ */
+static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ struct capdata_fan capdata_fan;
+ int i, err;
+
+ dev_dbg(dev, "Collecting fan info from capdata_fan\n");
+
+ if (!cd_fan_list)
+ goto out;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
+ if (err)
+ continue;
+
+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
+ }
+
+out:
+ priv->fan_flags.capdata_fan_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
+
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
@@ -561,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
device_unregister(priv->fw_attr_dev);
}
+/* ======== Self (master: lenovo-wmi-other) ======== */
+
/**
* lwmi_om_master_bind() - Bind all components of the other mode driver
* @dev: The lenovo-wmi-other driver basic device.
*
- * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
- * lenovo-wmi-other master driver. On success, assign the capability data 01
- * list pointer to the driver data struct for later access. This pointer
- * is only valid while the capdata01 interface exists. Finally, register all
- * firmware attribute groups.
+ * Call component_bind_all to bind the lenovo-wmi-capdata devices to the
+ * lenovo-wmi-other master driver, with a callback to collect fan info from
+ * capdata_fan. On success, assign the capability data list pointers to the
+ * driver data struct for later access. These pointers are only valid while the
+ * capdata interfaces exist. Finally, collect fan info from capdata00 and
+ * register all firmware attribute groups. Note that the HWMON device is
+ * registered only if all fan info is collected. Hence, it is not registered
+ * here. See lwmi_om_fan_info_collect_cd00() and
+ * lwmi_om_fan_info_collect_cd_fan().
*
* Return: 0 on success, or an error code.
*/
static int lwmi_om_master_bind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
- struct cd01_list *tmp_list;
+ struct lwmi_cd_binder binder = {
+ .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
+ };
int ret;
- ret = component_bind_all(dev, &tmp_list);
+ lwmi_om_fan_info_init(priv);
+
+ ret = component_bind_all(dev, &binder);
if (ret)
return ret;
- priv->cd01_list = tmp_list;
- if (!priv->cd01_list)
+ priv->cd00_list = binder.cd00_list;
+ priv->cd01_list = binder.cd01_list;
+ if (!priv->cd00_list || !priv->cd01_list)
return -ENODEV;
+ lwmi_om_fan_info_collect_cd00(priv);
+
return lwmi_om_fw_attr_add(priv);
}
@@ -594,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev)
* lwmi_om_master_unbind() - Unbind all components of the other mode driver
* @dev: The lenovo-wmi-other driver basic device
*
- * Unregister all capability data attribute groups. Then call
- * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
- * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ * Unregister all firmware attribute groups and the HWMON device. Then call
+ * component_unbind_all to unbind lenovo-wmi-capdata devices from the
+ * lenovo-wmi-other master driver.
*/
static void lwmi_om_master_unbind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
+
+ lwmi_om_hwmon_remove(priv);
+
component_unbind_all(dev, NULL);
}
@@ -620,10 +1099,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
if (!priv)
return -ENOMEM;
+ /* Sentinel for on-demand ida_free(). */
+ priv->ida_id = -EIDRM;
+
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
+ lwmi_cd_match_add_all(&wdev->dev, &master_match);
if (IS_ERR(master_match))
return PTR_ERR(master_match);
@@ -636,7 +1118,10 @@ static void lwmi_other_remove(struct wmi_device *wdev)
struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
component_master_del(&wdev->dev, &lwmi_om_master_ops);
- ida_free(&lwmi_om_ida, priv->ida_id);
+
+ /* No IDA to free if the driver is never bound to its components. */
+ if (priv->ida_id >= 0)
+ ida_free(&lwmi_om_ida, priv->ida_id);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
@@ -657,9 +1142,10 @@ static struct wmi_driver lwmi_other_driver = {
module_wmi_driver(lwmi_other_driver);
-MODULE_IMPORT_NS("LENOVO_WMI_CD01");
+MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c
index 31b298dc5046..69887de36c9b 100644
--- a/drivers/platform/x86/lenovo/yogabook.c
+++ b/drivers/platform/x86/lenovo/yogabook.c
@@ -57,7 +57,7 @@ struct yogabook_data {
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
- uint8_t brightness;
+ u8 brightness;
};
static void yogabook_work(struct work_struct *work)
@@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev)
- return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
+ return dev_err_probe(dev, -ENODEV,
+ "Cannot find the touchpad device in ACPI tables\n");
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
- r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
+ r = dev_err_probe(dev, -ENODEV,
+ "Cannot find the digitizer device in ACPI tables\n");
goto error_put_devs;
}
@@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index 0f935532f250..3c9af441d133 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -88,6 +88,9 @@
#define EC_ADDR_GPU_TEMP 0x044F
+#define EC_ADDR_SYSTEM_ID 0x0456
+#define HAS_GPU BIT(7)
+
#define EC_ADDR_MAIN_FAN_RPM_1 0x0464
#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
@@ -122,11 +125,11 @@
#define CTGP_DB_DB_ENABLE BIT(1)
#define CTGP_DB_CTGP_ENABLE BIT(2)
-#define EC_ADDR_CTGP_OFFSET 0x0744
+#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744
-#define EC_ADDR_TPP_OFFSET 0x0745
+#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745
-#define EC_ADDR_MAX_TGP 0x0746
+#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746
#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
#define LIGHTBAR_APP_EXISTS BIT(0)
@@ -317,11 +320,13 @@
#define UNIWILL_FEATURE_LIGHTBAR BIT(3)
#define UNIWILL_FEATURE_BATTERY BIT(4)
#define UNIWILL_FEATURE_HWMON BIT(5)
+#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6)
struct uniwill_data {
struct device *dev;
acpi_handle handle;
struct regmap *regmap;
+ unsigned int features;
struct acpi_battery_hook hook;
unsigned int last_charge_ctrl;
struct mutex battery_lock; /* Protects the list of currently registered batteries */
@@ -341,12 +346,21 @@ struct uniwill_battery_entry {
struct power_supply *battery;
};
+struct uniwill_device_descriptor {
+ unsigned int features;
+ /* Executed during driver probing */
+ int (*probe)(struct uniwill_data *data);
+};
+
static bool force;
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n");
-/* Feature bitmask since the associated registers are not reliable */
-static unsigned int supported_features;
+/*
+ * Contains device specific data like the feature bitmap since
+ * the associated registers are not always reliable.
+ */
+static struct uniwill_device_descriptor device_descriptor __ro_after_init;
static const char * const uniwill_temp_labels[] = {
"CPU",
@@ -411,6 +425,12 @@ static const struct key_entry uniwill_keymap[] = {
{ KE_END }
};
+static inline bool uniwill_device_supports(struct uniwill_data *data,
+ unsigned int features)
+{
+ return (data->features & features) == features;
+}
+
static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
{
union acpi_object params[2] = {
@@ -498,6 +518,10 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_BAT_RED:
case EC_ADDR_LIGHTBAR_BAT_GREEN:
case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
return true;
default:
return false;
@@ -531,6 +555,11 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_BAT_RED:
case EC_ADDR_LIGHTBAR_BAT_GREEN:
case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_SYSTEM_ID:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
return true;
default:
return false;
@@ -786,6 +815,70 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri
static DEVICE_ATTR_RW(breathing_in_suspend);
+static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value > U8_MAX)
+ return -EINVAL;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static DEVICE_ATTR_RW(ctgp_offset);
+
+static int uniwill_nvidia_ctgp_init(struct uniwill_data *data)
+{
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static struct attribute *uniwill_attrs[] = {
/* Keyboard-related */
&dev_attr_fn_lock_toggle_enable.attr,
@@ -794,29 +887,39 @@ static struct attribute *uniwill_attrs[] = {
/* Lightbar-related */
&dev_attr_rainbow_animation.attr,
&dev_attr_breathing_in_suspend.attr,
+ /* Power-management-related */
+ &dev_attr_ctgp_offset.attr,
NULL
};
static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{
+ struct device *dev = kobj_to_dev(kobj);
+ struct uniwill_data *data = dev_get_drvdata(dev);
+
if (attr == &dev_attr_fn_lock_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_FN_LOCK_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_super_key_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_touchpad_toggle_enable.attr) {
- if (supported_features & UNIWILL_FEATURE_TOUCHPAD_TOGGLE)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
return attr->mode;
}
if (attr == &dev_attr_rainbow_animation.attr ||
attr == &dev_attr_breathing_in_suspend.attr) {
- if (supported_features & UNIWILL_FEATURE_LIGHTBAR)
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_ctgp_offset.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
return attr->mode;
}
@@ -944,7 +1047,7 @@ static int uniwill_hwmon_init(struct uniwill_data *data)
{
struct device *hdev;
- if (!(supported_features & UNIWILL_FEATURE_HWMON))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON))
return 0;
hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data,
@@ -1019,7 +1122,7 @@ static int uniwill_led_init(struct uniwill_data *data)
unsigned int value;
int ret;
- if (!(supported_features & UNIWILL_FEATURE_LIGHTBAR))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
return 0;
ret = devm_mutex_init(data->dev, &data->led_lock);
@@ -1232,7 +1335,7 @@ static int uniwill_battery_init(struct uniwill_data *data)
{
int ret;
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
ret = devm_mutex_init(data->dev, &data->battery_lock);
@@ -1361,6 +1464,19 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ data->features = device_descriptor.features;
+
+ /*
+ * Some devices might need to perform some device-specific initialization steps
+ * before the supported features are initialized. Because of this we have to call
+ * this callback just after the EC itself was initialized.
+ */
+ if (device_descriptor.probe) {
+ ret = device_descriptor.probe(data);
+ if (ret < 0)
+ return ret;
+ }
+
ret = uniwill_battery_init(data);
if (ret < 0)
return ret;
@@ -1373,6 +1489,10 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ ret = uniwill_nvidia_ctgp_init(data);
+ if (ret < 0)
+ return ret;
+
return uniwill_input_init(data);
}
@@ -1385,7 +1505,7 @@ static void uniwill_shutdown(struct platform_device *pdev)
static int uniwill_suspend_keyboard(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return 0;
/*
@@ -1397,7 +1517,7 @@ static int uniwill_suspend_keyboard(struct uniwill_data *data)
static int uniwill_suspend_battery(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
/*
@@ -1408,6 +1528,15 @@ static int uniwill_suspend_battery(struct uniwill_data *data)
return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
}
+static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
static int uniwill_suspend(struct device *dev)
{
struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1421,6 +1550,10 @@ static int uniwill_suspend(struct device *dev)
if (ret < 0)
return ret;
+ ret = uniwill_suspend_nvidia_ctgp(data);
+ if (ret < 0)
+ return ret;
+
regcache_cache_only(data->regmap, true);
regcache_mark_dirty(data->regmap);
@@ -1432,7 +1565,7 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
unsigned int value;
int ret;
- if (!(supported_features & UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY_TOGGLE))
return 0;
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
@@ -1448,13 +1581,22 @@ static int uniwill_resume_keyboard(struct uniwill_data *data)
static int uniwill_resume_battery(struct uniwill_data *data)
{
- if (!(supported_features & UNIWILL_FEATURE_BATTERY))
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
data->last_charge_ctrl);
}
+static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
static int uniwill_resume(struct device *dev)
{
struct uniwill_data *data = dev_get_drvdata(dev);
@@ -1470,7 +1612,11 @@ static int uniwill_resume(struct device *dev)
if (ret < 0)
return ret;
- return uniwill_resume_battery(data);
+ ret = uniwill_resume_battery(data);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_resume_nvidia_ctgp(data);
}
static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume);
@@ -1496,6 +1642,48 @@ static struct platform_driver uniwill_driver = {
.shutdown = uniwill_shutdown,
};
+static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_HWMON,
+};
+
+static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK_TOGGLE |
+ UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_LIGHTBAR |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_HWMON,
+};
+
+static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & HAS_GPU)
+ data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL;
+
+ return 0;
+};
+
+static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
+ .probe = phxarx1_phxaqf1_probe,
+};
+
+static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+};
+
+static struct uniwill_device_descriptor empty_descriptor __initdata = {};
+
static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
{
.ident = "XMG FUSION 15",
@@ -1503,6 +1691,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "XMG FUSION 15",
@@ -1510,6 +1699,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "Intel NUC x15",
@@ -1517,11 +1707,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"),
},
- .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
- UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
- UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
- UNIWILL_FEATURE_BATTERY |
- UNIWILL_FEATURE_HWMON),
+ .driver_data = &lapac71h_descriptor,
},
{
.ident = "Intel NUC x15",
@@ -1529,12 +1715,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
},
- .driver_data = (void *)(UNIWILL_FEATURE_FN_LOCK_TOGGLE |
- UNIWILL_FEATURE_SUPER_KEY_TOGGLE |
- UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
- UNIWILL_FEATURE_LIGHTBAR |
- UNIWILL_FEATURE_BATTERY |
- UNIWILL_FEATURE_HWMON),
+ .driver_data = &lapkc71f_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1542,6 +1723,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
@@ -1549,6 +1731,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
@@ -1556,6 +1739,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
},
+ .driver_data = &phxarx1_phxaqf1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7",
@@ -1563,6 +1747,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -1570,6 +1755,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
@@ -1577,6 +1763,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
@@ -1584,6 +1771,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
@@ -1591,6 +1779,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
@@ -1598,6 +1787,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1605,6 +1795,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
@@ -1612,6 +1803,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
@@ -1619,6 +1811,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
@@ -1626,6 +1819,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1633,6 +1827,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
@@ -1640,6 +1835,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
@@ -1647,6 +1843,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
@@ -1654,6 +1851,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1661,6 +1859,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 AMD",
@@ -1668,6 +1867,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1675,6 +1875,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 AMD",
@@ -1682,6 +1883,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1689,6 +1891,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15 Gen1 Intel",
@@ -1696,6 +1899,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1703,6 +1907,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 17 Gen1 Intel",
@@ -1710,6 +1915,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Trinity 15 Intel Gen1",
@@ -1717,6 +1923,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Trinity 17 Intel Gen1",
@@ -1724,6 +1931,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 AMD",
@@ -1731,6 +1939,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen2 Intel",
@@ -1738,6 +1947,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
@@ -1745,6 +1955,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
@@ -1752,6 +1963,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
@@ -1759,6 +1971,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 15 Gen4 Intel",
@@ -1766,6 +1979,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Polaris 15/17 Gen5 AMD",
@@ -1773,6 +1987,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen5 AMD",
@@ -1780,6 +1995,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5",
@@ -1787,6 +2003,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
@@ -1794,6 +2011,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
@@ -1801,6 +2019,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1808,6 +2027,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
@@ -1815,6 +2035,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
@@ -1822,6 +2043,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 AMD",
@@ -1829,6 +2051,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1836,6 +2059,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
@@ -1843,6 +2067,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
},
+ .driver_data = &tux_featureset_1_descriptor,
},
{
.ident = "TUXEDO Book BA15 Gen10 AMD",
@@ -1850,6 +2075,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 14 Gen1 AMD",
@@ -1857,6 +2083,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen1 AMD",
@@ -1864,6 +2091,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
},
+ .driver_data = &empty_descriptor,
},
{
.ident = "TUXEDO Pulse 15 Gen2 AMD",
@@ -1871,6 +2099,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
},
+ .driver_data = &empty_descriptor,
},
{ }
};
@@ -1878,6 +2107,7 @@ MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table);
static int __init uniwill_init(void)
{
+ const struct uniwill_device_descriptor *descriptor;
const struct dmi_system_id *id;
int ret;
@@ -1887,10 +2117,22 @@ static int __init uniwill_init(void)
return -ENODEV;
/* Assume that the device supports all features */
- supported_features = UINT_MAX;
+ device_descriptor.features = UINT_MAX;
pr_warn("Loading on a potentially unsupported device\n");
} else {
- supported_features = (uintptr_t)id->driver_data;
+ /*
+ * Some devices might support additional features depending on
+ * the BIOS version/date, so we call this callback to let them
+ * modify their device descriptor accordingly.
+ */
+ if (id->callback) {
+ ret = id->callback(id);
+ if (ret < 0)
+ return ret;
+ }
+
+ descriptor = id->driver_data;
+ device_descriptor = *descriptor;
}
ret = platform_driver_register(&uniwill_driver);
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c
index 5b00370a9a22..e3a126de421b 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/x86/wmi-bmof.c
@@ -8,7 +8,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
@@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
+ return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length);
}
static const BIN_ATTR_ADMIN_RO(bmof, 0);
@@ -39,9 +38,9 @@ static const struct bin_attribute * const bmof_attrs[] = {
static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return obj->buffer.length;
+ return buffer->length;
}
static const struct attribute_group bmof_group = {
@@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = {
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
{
- union acpi_object *obj;
+ struct wmi_buffer *buffer;
+ int ret;
- obj = wmidev_block_query(wdev, 0);
- if (!obj) {
- dev_err(&wdev->dev, "failed to read Binary MOF\n");
- return -EIO;
- }
+ buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
- if (obj->type != ACPI_TYPE_BUFFER) {
- dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
- kfree(obj);
- return -EIO;
- }
+ ret = wmidev_query_block(wdev, 0, buffer);
+ if (ret < 0)
+ return ret;
- dev_set_drvdata(&wdev->dev, obj);
+ dev_set_drvdata(&wdev->dev, buffer);
return 0;
}
static void wmi_bmof_remove(struct wmi_device *wdev)
{
- union acpi_object *obj = dev_get_drvdata(&wdev->dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev);
- kfree(obj);
+ kfree(buffer->data);
}
static const struct wmi_device_id wmi_bmof_id_table[] = {
diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c
index b892007b9863..badf9e42e015 100644
--- a/drivers/platform/x86/xiaomi-wmi.c
+++ b/drivers/platform/x86/xiaomi-wmi.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Xiaomi Laptops */
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/module.h>
@@ -56,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return input_register_device(data->input_dev);
}
-static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
+static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy)
{
struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
@@ -85,7 +84,7 @@ static struct wmi_driver xiaomi_wmi_driver = {
},
.id_table = xiaomi_wmi_id_table,
.probe = xiaomi_wmi_probe,
- .notify = xiaomi_wmi_notify,
+ .notify_new = xiaomi_wmi_notify,
.no_singleton = true,
};
module_wmi_driver(xiaomi_wmi_driver);