* [PATCH 0/3] platform/x86: uniwill-laptop: Support the PCSpecialist Recoil 16 AMD
@ 2026-09-25 21:16 Mohamed Jouini
2026-09-25 21:16 ` [PATCH 1/3] platform/x86: uniwill-laptop: Add " Mohamed Jouini
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Mohamed Jouini @ 2026-09-25 21:16 UTC (permalink / raw)
To: Armin Wolf, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel, Mohamed Jouini
The PCSpecialist Recoil 16 AMD is a rebadged TUXEDO Stellaris 16 Gen7 AMD
(board X6FR57TY), which the driver does not match today because of the
different DMI vendor and board name.
Patch 1 adds it, using the percentage charge limit rather than the
charging profiles: on this firmware the "balanced" profile does not limit
charging, while EC_ADDR_CHARGE_CTRL does and is reported by _BST.
Patch 2 adds platform profile support for the three firmware power modes
("office", "balance", "turbo") in EC_ADDR_MANUAL_FAN_CTRL, and handles the
mode key. Writing the mode makes the EC apply it fully (mode LED, NVIDIA
Dynamic Boost/TPP limits, _Q88 notification). It is only enabled for the
Recoil 16 for now, but the same register layout is probably present on
other Uniwill models.
Patch 3 maps the "Sc" key (WMI event 0xD0) to KEY_ROTATE_DISPLAY.
Tested on the Recoil 16 AMD (BIOS N.1.39PCS10), with the driver from
this series built against 7.2.6: platform profile from
power-profiles-daemon and the mode key (LED colour and GPU power limits
follow), the Sc key, Fn lock and Super key lock, hwmon, the charge limit
attribute, and the AC auto-boot / USB powershare attributes (read only).
With the same changes on top of the 7.2 driver, charging also stopped at
the configured limit with _BST reporting "Not charging".
Mohamed Jouini (3):
platform/x86: uniwill-laptop: Add PCSpecialist Recoil 16 AMD
platform/x86: uniwill-laptop: Add platform profile support
platform/x86: uniwill-laptop: Map the screen rotation key
drivers/platform/x86/uniwill/uniwill-acpi.c | 155 ++++++++++++++++++++
drivers/platform/x86/uniwill/uniwill-wmi.h | 3 +
2 files changed, 158 insertions(+)
base-commit: f475845eaf3d749114a63270bf2efea459e14dd2
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/3] platform/x86: uniwill-laptop: Add PCSpecialist Recoil 16 AMD
2026-09-25 21:16 [PATCH 0/3] platform/x86: uniwill-laptop: Support the PCSpecialist Recoil 16 AMD Mohamed Jouini
@ 2026-09-25 21:16 ` Mohamed Jouini
2026-09-25 23:44 ` Armin Wolf
2026-09-25 21:16 ` [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support Mohamed Jouini
2026-09-25 21:16 ` [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key Mohamed Jouini
2 siblings, 1 reply; 9+ messages in thread
From: Mohamed Jouini @ 2026-09-25 21:16 UTC (permalink / raw)
To: Armin Wolf, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel, Mohamed Jouini
The PCSpecialist Recoil 16 AMD is a rebadged TUXEDO Stellaris 16 Gen7 AMD
but reports sys_vendor "PCSpecialist" and board name "X6FR57TY", so the
existing entry does not match it.
Use the Stellaris 16 Gen7 AMD feature set, but with the percentage charge
limit instead of the charging profiles: on this firmware the "balanced"
profile does not limit charging (the battery still charges to full),
whereas the ACPI _BST method reads EC_ADDR_CHARGE_CTRL (CGLM) and reports
the "charge limiting" state bit while it is set to 1-99. Setting
charge_control_end_threshold to 90 on a full battery switches the
reported status from "Full" to "Not charging", and charging stops at 90%.
Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
---
drivers/platform/x86/uniwill/uniwill-acpi.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index eeead0159..beba5a76a 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -2787,6 +2787,19 @@ static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __ini
UNIWILL_FEATURE_USB_POWERSHARE,
};
+static struct uniwill_device_descriptor pcs_recoil16_amd_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_AC_AUTO_BOOT |
+ UNIWILL_FEATURE_USB_POWERSHARE,
+};
+
static int phxtxx1_probe(struct uniwill_data *data)
{
unsigned int value;
@@ -3268,6 +3281,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
},
.driver_data = &tux_featureset_4_nvidia_descriptor,
},
+ {
+ .ident = "PCSpecialist Recoil 16 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR57TY"),
+ },
+ .driver_data = &pcs_recoil16_amd_descriptor,
+ },
{
.ident = "TUXEDO Stellaris 16 Gen7 Intel",
.matches = {
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support
2026-09-25 21:16 [PATCH 0/3] platform/x86: uniwill-laptop: Support the PCSpecialist Recoil 16 AMD Mohamed Jouini
2026-09-25 21:16 ` [PATCH 1/3] platform/x86: uniwill-laptop: Add " Mohamed Jouini
@ 2026-09-25 21:16 ` Mohamed Jouini
2026-09-25 23:54 ` Armin Wolf
2026-09-25 21:16 ` [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key Mohamed Jouini
2 siblings, 1 reply; 9+ messages in thread
From: Mohamed Jouini @ 2026-09-25 21:16 UTC (permalink / raw)
To: Armin Wolf, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel, Mohamed Jouini
The EC exposes the three firmware power modes (BIOS "office", "balance"
and "turbo") in EC_ADDR_MANUAL_FAN_CTRL. The ACPI tables name BIT(4) TBME
and BIT(7) UFME and only evaluate those two bits; writing the register
makes the EC apply the whole mode: mode LED colour, NVIDIA Dynamic
Boost/TPP limits (DBAP/ETPP) and the _Q88 mode-change notification.
Expose the modes as low-power/balanced/performance platform profiles on
the PCSpecialist Recoil 16 AMD and handle the mode key: the EC only
cycles the mode itself until the OS sets one, afterwards it just reports
UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, so notify userspace if the EC
already switched and cycle the profile otherwise.
Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
---
drivers/platform/x86/uniwill/uniwill-acpi.c | 133 +++++++++++++++++++-
1 file changed, 132 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index beba5a76a..0932e5937 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -39,6 +39,7 @@
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
+#include <linux/platform_profile.h>
#include <linux/pm.h>
#include <linux/printk.h>
#include <linux/regmap.h>
@@ -184,6 +185,15 @@
#define FAN_MODE_HIGH BIT(5)
#define FAN_MODE_BOOST BIT(6)
#define FAN_MODE_USER BIT(7)
+/*
+ * Firmware power modes (BIOS "office/balance/turbo"). The ACPI tables name
+ * BIT(4) TBME and BIT(7) UFME and only evaluate those two bits; the values
+ * below are exactly what the EC itself stores when the mode button is pressed.
+ */
+#define PERF_MODE_MASK (FAN_MODE_USER | FAN_MODE_HIGH | FAN_MODE_TURBO)
+#define PERF_MODE_OFFICE (FAN_MODE_USER | FAN_MODE_HIGH)
+#define PERF_MODE_BALANCE FAN_MODE_HIGH
+#define PERF_MODE_TURBO (FAN_MODE_HIGH | FAN_MODE_TURBO)
#define EC_ADDR_PWM_1 0x075B
@@ -369,6 +379,7 @@
#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12)
#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13)
#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14)
+#define UNIWILL_FEATURE_PERFORMANCE_MODES BIT(15)
enum usb_c_power_priority_options {
USB_C_POWER_PRIORITY_CHARGING = 0,
@@ -418,6 +429,8 @@ struct uniwill_data {
struct notifier_block nb;
struct mutex usb_c_power_priority_lock; /* Protects dependent bit write and state safe */
enum usb_c_power_priority_options last_usb_c_power_priority_option;
+ struct device *ppdev;
+ enum platform_profile_option last_profile; /* last profile read or written */
};
struct uniwill_battery_entry {
@@ -602,6 +615,7 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_AC_GREEN:
case EC_ADDR_LIGHTBAR_AC_BLUE:
case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_MANUAL_FAN_CTRL:
case EC_ADDR_TRIGGER:
case EC_ADDR_RGB_RED:
case EC_ADDR_RGB_GREEN:
@@ -643,6 +657,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
case EC_ADDR_LIGHTBAR_AC_GREEN:
case EC_ADDR_LIGHTBAR_AC_BLUE:
case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_MANUAL_FAN_CTRL:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
case EC_ADDR_SUPPORT_2:
@@ -682,6 +697,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
case EC_ADDR_SECOND_FAN_RPM_2:
case EC_ADDR_BAT_ALERT:
case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_MANUAL_FAN_CTRL:
case EC_ADDR_PWM_1:
case EC_ADDR_PWM_2:
case EC_ADDR_SUPPORT_2:
@@ -2168,6 +2184,110 @@ static int uniwill_battery_init(struct uniwill_data *data)
return devm_battery_hook_register(data->dev, &data->hook);
}
+static int uniwill_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+
+ return 0;
+}
+
+static int uniwill_platform_profile_get(struct device *dev, enum platform_profile_option *profile)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_MANUAL_FAN_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ /* Same decision as the firmware's PMSC method */
+ switch (value & (FAN_MODE_USER | FAN_MODE_TURBO)) {
+ case FAN_MODE_USER:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case FAN_MODE_TURBO:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ default:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ }
+
+ WRITE_ONCE(data->last_profile, *profile);
+
+ return 0;
+}
+
+static int uniwill_platform_profile_set(struct device *dev, enum platform_profile_option profile)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ value = PERF_MODE_OFFICE;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ value = PERF_MODE_BALANCE;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ value = PERF_MODE_TURBO;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* The EC applies the mode (LED, power limits, ACPI notifications) itself */
+ ret = regmap_update_bits(data->regmap, EC_ADDR_MANUAL_FAN_CTRL, PERF_MODE_MASK, value);
+ if (ret < 0)
+ return ret;
+
+ WRITE_ONCE(data->last_profile, profile);
+
+ return 0;
+}
+
+static const struct platform_profile_ops uniwill_platform_profile_ops = {
+ .probe = uniwill_platform_profile_probe,
+ .profile_get = uniwill_platform_profile_get,
+ .profile_set = uniwill_platform_profile_set,
+};
+
+static int uniwill_platform_profile_init(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_PERFORMANCE_MODES))
+ return 0;
+
+ data->ppdev = devm_platform_profile_register(data->dev, DRIVER_NAME, data,
+ &uniwill_platform_profile_ops);
+ if (IS_ERR(data->ppdev))
+ return PTR_ERR(data->ppdev);
+
+ return uniwill_platform_profile_get(data->ppdev, &data->last_profile);
+}
+
+/*
+ * The EC only cycles the mode on its own until the OS sets a mode once; after
+ * that the button just reports UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE. Handle
+ * both cases: if the EC already switched, only notify userspace, otherwise
+ * cycle the profile ourselves.
+ */
+static void uniwill_platform_profile_button(struct uniwill_data *data)
+{
+ enum platform_profile_option before = READ_ONCE(data->last_profile), now;
+
+ msleep(100); /* give the EC time to apply its own switch, if any */
+
+ if (!uniwill_platform_profile_get(data->ppdev, &now) && now != before)
+ platform_profile_notify(data->ppdev);
+ else
+ platform_profile_cycle();
+}
+
static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy)
{
struct uniwill_data *data = container_of(nb, struct uniwill_data, nb);
@@ -2239,6 +2359,12 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
return NOTIFY_DONE;
return notifier_from_errno(uniwill_notify_kbd_led(data, 4));
+ case UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE:
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_PERFORMANCE_MODES)) {
+ uniwill_platform_profile_button(data);
+ return NOTIFY_OK;
+ }
+ fallthrough;
default:
mutex_lock(&data->input_lock);
sparse_keymap_report_event(data->input_device, action, 1, true);
@@ -2375,6 +2501,10 @@ static int uniwill_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ ret = uniwill_platform_profile_init(data);
+ if (ret < 0)
+ return ret;
+
return uniwill_input_init(data);
}
@@ -2797,7 +2927,8 @@ static struct uniwill_device_descriptor pcs_recoil16_amd_descriptor __initdata =
UNIWILL_FEATURE_SECONDARY_FAN |
UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
UNIWILL_FEATURE_AC_AUTO_BOOT |
- UNIWILL_FEATURE_USB_POWERSHARE,
+ UNIWILL_FEATURE_USB_POWERSHARE |
+ UNIWILL_FEATURE_PERFORMANCE_MODES,
};
static int phxtxx1_probe(struct uniwill_data *data)
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key
2026-09-25 21:16 [PATCH 0/3] platform/x86: uniwill-laptop: Support the PCSpecialist Recoil 16 AMD Mohamed Jouini
2026-09-25 21:16 ` [PATCH 1/3] platform/x86: uniwill-laptop: Add " Mohamed Jouini
2026-09-25 21:16 ` [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support Mohamed Jouini
@ 2026-09-25 21:16 ` Mohamed Jouini
2026-09-25 23:52 ` Armin Wolf
2 siblings, 1 reply; 9+ messages in thread
From: Mohamed Jouini @ 2026-09-25 21:16 UTC (permalink / raw)
To: Armin Wolf, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel, Mohamed Jouini
The PCSpecialist Recoil 16 AMD / TUXEDO Stellaris 16 Gen7 chassis has an
"Sc" key next to F12, meant to rotate the lay-flat screen by 180 degrees.
It reports WMI event 0xD0, which is currently reported as KEY_UNKNOWN.
Map it to KEY_ROTATE_DISPLAY.
Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
---
drivers/platform/x86/uniwill/uniwill-acpi.c | 3 +++
drivers/platform/x86/uniwill/uniwill-wmi.h | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
index 0932e5937..f99c5e9ac 100644
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -519,6 +519,9 @@ static const struct key_entry uniwill_keymap[] = {
/* Reported when the user wants to toggle the webcam */
{ KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
+ /* Reported when the user wants to rotate the (lay-flat) screen by 180 degrees */
+ { KE_KEY, UNIWILL_OSD_ROTATE_SCREEN, { KEY_ROTATE_DISPLAY }},
+
{ KE_END }
};
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
index b25b2f312..1b6f650c4 100644
--- a/drivers/platform/x86/uniwill/uniwill-wmi.h
+++ b/drivers/platform/x86/uniwill/uniwill-wmi.h
@@ -117,6 +117,9 @@
#define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
+/* "Sc" key next to F12 on the PCSpecialist Recoil 16 / Stellaris 16 Gen7 */
+#define UNIWILL_OSD_ROTATE_SCREEN 0xD0
+
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
struct device;
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] platform/x86: uniwill-laptop: Add PCSpecialist Recoil 16 AMD
2026-09-25 21:16 ` [PATCH 1/3] platform/x86: uniwill-laptop: Add " Mohamed Jouini
@ 2026-09-25 23:44 ` Armin Wolf
2026-09-26 0:24 ` Am Dev
0 siblings, 1 reply; 9+ messages in thread
From: Armin Wolf @ 2026-09-25 23:44 UTC (permalink / raw)
To: Mohamed Jouini, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel
Am 25.09.26 um 23:16 schrieb Mohamed Jouini:
> The PCSpecialist Recoil 16 AMD is a rebadged TUXEDO Stellaris 16 Gen7 AMD
> but reports sys_vendor "PCSpecialist" and board name "X6FR57TY", so the
> existing entry does not match it.
>
> Use the Stellaris 16 Gen7 AMD feature set, but with the percentage charge
> limit instead of the charging profiles: on this firmware the "balanced"
> profile does not limit charging (the battery still charges to full),
> whereas the ACPI _BST method reads EC_ADDR_CHARGE_CTRL (CGLM) and reports
> the "charge limiting" state bit while it is set to 1-99. Setting
> charge_control_end_threshold to 90 on a full battery switches the
> reported status from "Full" to "Not charging", and charging stops at 90%.
Hi,
it is to be expected that the EC firmware will emulate a full charge to 100 percent even
when battery health conservation is enabled. I strongly warn you against enabling the
charge limit on machines other that Intel NUC-based models because apparently Uniwill
considers the charge limit feature as a preview on all other models, so accessing it
might permanently damage you battery!
I suggest you continue to use the battery charge modes.
Thanks,
Armin Wolf
> Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
> ---
> drivers/platform/x86/uniwill/uniwill-acpi.c | 21 +++++++++++++++++++++
> 1 file changed, 21 insertions(+)
>
> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
> index eeead0159..beba5a76a 100644
> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
> @@ -2787,6 +2787,19 @@ static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __ini
> UNIWILL_FEATURE_USB_POWERSHARE,
> };
>
> +static struct uniwill_device_descriptor pcs_recoil16_amd_descriptor __initdata = {
> + .features = UNIWILL_FEATURE_FN_LOCK |
> + UNIWILL_FEATURE_SUPER_KEY |
> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
> + UNIWILL_FEATURE_CPU_TEMP |
> + UNIWILL_FEATURE_GPU_TEMP |
> + UNIWILL_FEATURE_PRIMARY_FAN |
> + UNIWILL_FEATURE_SECONDARY_FAN |
> + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
> + UNIWILL_FEATURE_AC_AUTO_BOOT |
> + UNIWILL_FEATURE_USB_POWERSHARE,
> +};
> +
> static int phxtxx1_probe(struct uniwill_data *data)
> {
> unsigned int value;
> @@ -3268,6 +3281,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
> },
> .driver_data = &tux_featureset_4_nvidia_descriptor,
> },
> + {
> + .ident = "PCSpecialist Recoil 16 AMD",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR57TY"),
> + },
> + .driver_data = &pcs_recoil16_amd_descriptor,
> + },
> {
> .ident = "TUXEDO Stellaris 16 Gen7 Intel",
> .matches = {
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key
2026-09-25 21:16 ` [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key Mohamed Jouini
@ 2026-09-25 23:52 ` Armin Wolf
0 siblings, 0 replies; 9+ messages in thread
From: Armin Wolf @ 2026-09-25 23:52 UTC (permalink / raw)
To: Mohamed Jouini, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel, Werner Sembach
Am 25.09.26 um 23:16 schrieb Mohamed Jouini:
> The PCSpecialist Recoil 16 AMD / TUXEDO Stellaris 16 Gen7 chassis has an
> "Sc" key next to F12, meant to rotate the lay-flat screen by 180 degrees.
> It reports WMI event 0xD0, which is currently reported as KEY_UNKNOWN.
> Map it to KEY_ROTATE_DISPLAY.
Unless Tuxedo know more about this event code:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
> ---
> drivers/platform/x86/uniwill/uniwill-acpi.c | 3 +++
> drivers/platform/x86/uniwill/uniwill-wmi.h | 3 +++
> 2 files changed, 6 insertions(+)
>
> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
> index 0932e5937..f99c5e9ac 100644
> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
> @@ -519,6 +519,9 @@ static const struct key_entry uniwill_keymap[] = {
> /* Reported when the user wants to toggle the webcam */
> { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
>
> + /* Reported when the user wants to rotate the (lay-flat) screen by 180 degrees */
> + { KE_KEY, UNIWILL_OSD_ROTATE_SCREEN, { KEY_ROTATE_DISPLAY }},
> +
> { KE_END }
> };
>
> diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
> index b25b2f312..1b6f650c4 100644
> --- a/drivers/platform/x86/uniwill/uniwill-wmi.h
> +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h
> @@ -117,6 +117,9 @@
>
> #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
>
> +/* "Sc" key next to F12 on the PCSpecialist Recoil 16 / Stellaris 16 Gen7 */
> +#define UNIWILL_OSD_ROTATE_SCREEN 0xD0
> +
> #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
>
> struct device;
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support
2026-09-25 21:16 ` [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support Mohamed Jouini
@ 2026-09-25 23:54 ` Armin Wolf
2026-09-26 0:33 ` Am Dev
0 siblings, 1 reply; 9+ messages in thread
From: Armin Wolf @ 2026-09-25 23:54 UTC (permalink / raw)
To: Mohamed Jouini, Hans de Goede, Ilpo Järvinen
Cc: platform-driver-x86, linux-kernel
Am 25.09.26 um 23:16 schrieb Mohamed Jouini:
> The EC exposes the three firmware power modes (BIOS "office", "balance"
> and "turbo") in EC_ADDR_MANUAL_FAN_CTRL. The ACPI tables name BIT(4) TBME
> and BIT(7) UFME and only evaluate those two bits; writing the register
> makes the EC apply the whole mode: mode LED colour, NVIDIA Dynamic
> Boost/TPP limits (DBAP/ETPP) and the _Q88 mode-change notification.
>
> Expose the modes as low-power/balanced/performance platform profiles on
> the PCSpecialist Recoil 16 AMD and handle the mode key: the EC only
> cycles the mode itself until the OS sets one, afterwards it just reports
> UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, so notify userspace if the EC
> already switched and cycle the profile otherwise.
Nice work, but AFAIK the whole fan control/platform profile complex needs a bit more
work before we can enable this feature. Can we skip this patch for now?
Thanks,
Armin Wolf
> Signed-off-by: Mohamed Jouini <amad3v@gmail.com>
> ---
> drivers/platform/x86/uniwill/uniwill-acpi.c | 133 +++++++++++++++++++-
> 1 file changed, 132 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
> index beba5a76a..0932e5937 100644
> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
> @@ -39,6 +39,7 @@
> #include <linux/mutex.h>
> #include <linux/notifier.h>
> #include <linux/platform_device.h>
> +#include <linux/platform_profile.h>
> #include <linux/pm.h>
> #include <linux/printk.h>
> #include <linux/regmap.h>
> @@ -184,6 +185,15 @@
> #define FAN_MODE_HIGH BIT(5)
> #define FAN_MODE_BOOST BIT(6)
> #define FAN_MODE_USER BIT(7)
> +/*
> + * Firmware power modes (BIOS "office/balance/turbo"). The ACPI tables name
> + * BIT(4) TBME and BIT(7) UFME and only evaluate those two bits; the values
> + * below are exactly what the EC itself stores when the mode button is pressed.
> + */
> +#define PERF_MODE_MASK (FAN_MODE_USER | FAN_MODE_HIGH | FAN_MODE_TURBO)
> +#define PERF_MODE_OFFICE (FAN_MODE_USER | FAN_MODE_HIGH)
> +#define PERF_MODE_BALANCE FAN_MODE_HIGH
> +#define PERF_MODE_TURBO (FAN_MODE_HIGH | FAN_MODE_TURBO)
>
> #define EC_ADDR_PWM_1 0x075B
>
> @@ -369,6 +379,7 @@
> #define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12)
> #define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13)
> #define UNIWILL_FEATURE_USB_POWERSHARE BIT(14)
> +#define UNIWILL_FEATURE_PERFORMANCE_MODES BIT(15)
>
> enum usb_c_power_priority_options {
> USB_C_POWER_PRIORITY_CHARGING = 0,
> @@ -418,6 +429,8 @@ struct uniwill_data {
> struct notifier_block nb;
> struct mutex usb_c_power_priority_lock; /* Protects dependent bit write and state safe */
> enum usb_c_power_priority_options last_usb_c_power_priority_option;
> + struct device *ppdev;
> + enum platform_profile_option last_profile; /* last profile read or written */
> };
>
> struct uniwill_battery_entry {
> @@ -602,6 +615,7 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
> case EC_ADDR_LIGHTBAR_AC_GREEN:
> case EC_ADDR_LIGHTBAR_AC_BLUE:
> case EC_ADDR_BIOS_OEM:
> + case EC_ADDR_MANUAL_FAN_CTRL:
> case EC_ADDR_TRIGGER:
> case EC_ADDR_RGB_RED:
> case EC_ADDR_RGB_GREEN:
> @@ -643,6 +657,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
> case EC_ADDR_LIGHTBAR_AC_GREEN:
> case EC_ADDR_LIGHTBAR_AC_BLUE:
> case EC_ADDR_BIOS_OEM:
> + case EC_ADDR_MANUAL_FAN_CTRL:
> case EC_ADDR_PWM_1:
> case EC_ADDR_PWM_2:
> case EC_ADDR_SUPPORT_2:
> @@ -682,6 +697,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
> case EC_ADDR_SECOND_FAN_RPM_2:
> case EC_ADDR_BAT_ALERT:
> case EC_ADDR_BIOS_OEM:
> + case EC_ADDR_MANUAL_FAN_CTRL:
> case EC_ADDR_PWM_1:
> case EC_ADDR_PWM_2:
> case EC_ADDR_SUPPORT_2:
> @@ -2168,6 +2184,110 @@ static int uniwill_battery_init(struct uniwill_data *data)
> return devm_battery_hook_register(data->dev, &data->hook);
> }
>
> +static int uniwill_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> +
> + return 0;
> +}
> +
> +static int uniwill_platform_profile_get(struct device *dev, enum platform_profile_option *profile)
> +{
> + struct uniwill_data *data = dev_get_drvdata(dev);
> + unsigned int value;
> + int ret;
> +
> + ret = regmap_read(data->regmap, EC_ADDR_MANUAL_FAN_CTRL, &value);
> + if (ret < 0)
> + return ret;
> +
> + /* Same decision as the firmware's PMSC method */
> + switch (value & (FAN_MODE_USER | FAN_MODE_TURBO)) {
> + case FAN_MODE_USER:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + break;
> + case FAN_MODE_TURBO:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + break;
> + default:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + break;
> + }
> +
> + WRITE_ONCE(data->last_profile, *profile);
> +
> + return 0;
> +}
> +
> +static int uniwill_platform_profile_set(struct device *dev, enum platform_profile_option profile)
> +{
> + struct uniwill_data *data = dev_get_drvdata(dev);
> + unsigned int value;
> + int ret;
> +
> + switch (profile) {
> + case PLATFORM_PROFILE_LOW_POWER:
> + value = PERF_MODE_OFFICE;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + value = PERF_MODE_BALANCE;
> + break;
> + case PLATFORM_PROFILE_PERFORMANCE:
> + value = PERF_MODE_TURBO;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + /* The EC applies the mode (LED, power limits, ACPI notifications) itself */
> + ret = regmap_update_bits(data->regmap, EC_ADDR_MANUAL_FAN_CTRL, PERF_MODE_MASK, value);
> + if (ret < 0)
> + return ret;
> +
> + WRITE_ONCE(data->last_profile, profile);
> +
> + return 0;
> +}
> +
> +static const struct platform_profile_ops uniwill_platform_profile_ops = {
> + .probe = uniwill_platform_profile_probe,
> + .profile_get = uniwill_platform_profile_get,
> + .profile_set = uniwill_platform_profile_set,
> +};
> +
> +static int uniwill_platform_profile_init(struct uniwill_data *data)
> +{
> + if (!uniwill_device_supports(data, UNIWILL_FEATURE_PERFORMANCE_MODES))
> + return 0;
> +
> + data->ppdev = devm_platform_profile_register(data->dev, DRIVER_NAME, data,
> + &uniwill_platform_profile_ops);
> + if (IS_ERR(data->ppdev))
> + return PTR_ERR(data->ppdev);
> +
> + return uniwill_platform_profile_get(data->ppdev, &data->last_profile);
> +}
> +
> +/*
> + * The EC only cycles the mode on its own until the OS sets a mode once; after
> + * that the button just reports UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE. Handle
> + * both cases: if the EC already switched, only notify userspace, otherwise
> + * cycle the profile ourselves.
> + */
> +static void uniwill_platform_profile_button(struct uniwill_data *data)
> +{
> + enum platform_profile_option before = READ_ONCE(data->last_profile), now;
> +
> + msleep(100); /* give the EC time to apply its own switch, if any */
> +
> + if (!uniwill_platform_profile_get(data->ppdev, &now) && now != before)
> + platform_profile_notify(data->ppdev);
> + else
> + platform_profile_cycle();
> +}
> +
> static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy)
> {
> struct uniwill_data *data = container_of(nb, struct uniwill_data, nb);
> @@ -2239,6 +2359,12 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
> return NOTIFY_DONE;
>
> return notifier_from_errno(uniwill_notify_kbd_led(data, 4));
> + case UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE:
> + if (uniwill_device_supports(data, UNIWILL_FEATURE_PERFORMANCE_MODES)) {
> + uniwill_platform_profile_button(data);
> + return NOTIFY_OK;
> + }
> + fallthrough;
> default:
> mutex_lock(&data->input_lock);
> sparse_keymap_report_event(data->input_device, action, 1, true);
> @@ -2375,6 +2501,10 @@ static int uniwill_probe(struct platform_device *pdev)
> if (ret < 0)
> return ret;
>
> + ret = uniwill_platform_profile_init(data);
> + if (ret < 0)
> + return ret;
> +
> return uniwill_input_init(data);
> }
>
> @@ -2797,7 +2927,8 @@ static struct uniwill_device_descriptor pcs_recoil16_amd_descriptor __initdata =
> UNIWILL_FEATURE_SECONDARY_FAN |
> UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
> UNIWILL_FEATURE_AC_AUTO_BOOT |
> - UNIWILL_FEATURE_USB_POWERSHARE,
> + UNIWILL_FEATURE_USB_POWERSHARE |
> + UNIWILL_FEATURE_PERFORMANCE_MODES,
> };
>
> static int phxtxx1_probe(struct uniwill_data *data)
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] platform/x86: uniwill-laptop: Add PCSpecialist Recoil 16 AMD
2026-09-25 23:44 ` Armin Wolf
@ 2026-09-26 0:24 ` Am Dev
0 siblings, 0 replies; 9+ messages in thread
From: Am Dev @ 2026-09-26 0:24 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, platform-driver-x86, linux-kernel
On Sat, 26 Sept 2026 at 00:44, Armin Wolf <W_Armin@gmx.de> wrote:
>
> it is to be expected that the EC firmware will emulate a full charge to 100 percent even
> when battery health conservation is enabled. I strongly warn you against enabling the
> charge limit on machines other that Intel NUC-based models because apparently Uniwill
> considers the charge limit feature as a preview on all other models, so accessing it
> might permanently damage you battery!
>
> I suggest you continue to use the battery charge modes.
>
Hi Armin,
thanks for the warning, I did not know that the charge limit is only a
preview feature on these models. I have stopped using it and will switch
to the battery charge modes in v2.
One question: the EC originally had 0 in EC_ADDR_CHARGE_CTRL, and it is
now 100 after I lifted the limit through charge_control_end_threshold.
Is 100 safe to leave, or should it be restored to 0?
Thanks,
Mohamed
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support
2026-09-25 23:54 ` Armin Wolf
@ 2026-09-26 0:33 ` Am Dev
0 siblings, 0 replies; 9+ messages in thread
From: Am Dev @ 2026-09-26 0:33 UTC (permalink / raw)
To: Armin Wolf
Cc: Hans de Goede, Ilpo Järvinen, platform-driver-x86, linux-kernel
On Sat, 26 Sept 2026 at 00:54, Armin Wolf <W_Armin@gmx.de> wrote:
>
> Nice work, but AFAIK the whole fan control/platform profile complex needs a bit more
> work before we can enable this feature. Can we skip this patch for now?
Sure, I will drop it from v2. When you get to the fan control / platform
profile work, I am happy to test on the Recoil 16.
Thanks,
Mohamed
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-26 0:33 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-25 21:16 [PATCH 0/3] platform/x86: uniwill-laptop: Support the PCSpecialist Recoil 16 AMD Mohamed Jouini
2026-09-25 21:16 ` [PATCH 1/3] platform/x86: uniwill-laptop: Add " Mohamed Jouini
2026-09-25 23:44 ` Armin Wolf
2026-09-26 0:24 ` Am Dev
2026-09-25 21:16 ` [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support Mohamed Jouini
2026-09-25 23:54 ` Armin Wolf
2026-09-26 0:33 ` Am Dev
2026-09-25 21:16 ` [PATCH 3/3] platform/x86: uniwill-laptop: Map the screen rotation key Mohamed Jouini
2026-09-25 23:52 ` Armin Wolf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®