From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2AB8E4FB9B9 for ; Fri, 25 Sep 2026 21:16:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790371004; cv=none; b=IwZZ/++nIRcosuxS5yz+DG6JR/C7UpgicCuE7mawXNSEPodDL9EPLAcvY5fYd4dYec3hyoLPhzd2FRJqtH6wL3OC02FvxaCvdb1ncI0Zlph7TM+/X2zzC6CkZT+AyE96xLzP4BRCjoF6AhQrXaEKzzlG6WSAFuPhybmt9qj4ADM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790371004; c=relaxed/simple; bh=AW7GmJbvvUZLv/S3aozdsexkzu3edregCAMjxYRowE8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=abw10WAn+0qbbIlISD4McQwfXNvaxeWJYqxgmk6PWBByCONXH/GT3MlnHqbsiOTLo5XI8m+XykWZvTEJEUiczciXn8nB+vQplYARMXdzYeYWBvlw9/pLY9jcbBDK4JXGdVwQD3d+TZGtiYj/yQAKpq5XJLrbnrHoGRjEqR811fs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ECTrJ3Bp; arc=none smtp.client-ip=74.125.225.76 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ECTrJ3Bp" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-4843378fb37so781160f8f.3 for ; Fri, 25 Sep 2026 14:16:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790371000; x=1790975800; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=0OpiJYgEy2fH0EKJHkY2bvSEZYmp9XcXhKKG0UR6cbc=; b=ECTrJ3Bph+G1OdgMECUHzQy8XUfkZRQdpH0st22kO+bdFlwX5IJjoPRjvtDULRkxac lFszwQ9GSEclvRVGImCk6/nYUFtBE5OobCwmRyoLz+uWDjZcq/6mDXMVbudX33yGCYes RFIKB+f5VNsIkp5Hiq1wYhMCi/HOF2vfeLPW4r2eNAeEOuB/G0Ic7vI0NSrw8CQopYUK 2X8zj/pJT2U6SabUdRa06ZGRrmvlbqZd5RQthZn1+ydDrPTpIy8vg6D9B7JcKUt2g1vF cXS/d7CY/nVR0BvtYyj0bXK6TMH91uoZmAbSkwuYoQWXcMCC5n/Q2jXJM5ctXZw4eIiX zBmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790371000; x=1790975800; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=0OpiJYgEy2fH0EKJHkY2bvSEZYmp9XcXhKKG0UR6cbc=; b=c6f/qrbpTO3mRlbA+t/0m2d41gBMYuA5Uo8WS2HqGpvL3GdOEQLXrCouxEw9hpmC3L 0/my5iuRtEn8ZLRrDGFCAI04eANfiqCKiPKUs87eN49rFBYHpsQKsu1nefOa+WQO/dGN GkzuESXwgqm8q2u8kuWWma0dtOlWpm5RSKugq/zkMt7noZQiVcKAjQXc+Ay6S4jY6HxT bIUG7oRNwkvitj8qLbmKG7Wmpd7BabQivH4IFQWetfh2xtwalS2LVZGQShlHobrp5tiW BjlEiD7Xfuc6/4g4ieStLlfPrkW3dYa2j45+4xsEuqJMDQc8QxqMU+2Uk2R6yTyuduoV GDPQ== X-Forwarded-Encrypted: i=1; AKwUvBzANAk6Qf2AMS6TqBM0UdA9z/r/ujjidd96lkzXQC6LgSdf2Xj87m8gmPbv8U4VRXLRBupUqWrIUC/hPjE=@vger.kernel.org X-Gm-Message-State: AFuF++k7j6X4DrEbnZHvEQFcBlAzQrCfY68rncE0NCoEL0XIkAp8bgI1 nqEi+8NymO0n05/pUA8SZSc3xEj+bIAXAVz/MPcw/oe88fl6/TTrbije X-Gm-Gg: AYBFou2BUfAXjNE190/mxSH5glGHrPBR+B4xruTUzMzArmcbArvWuY1PsruFCOWKk5D mn/Y+4R6HJnE7cBYAnAc2W2rs3YJfEtDUV4YyDXhpVpVE6spfpZr18wdA1emL/N6ar5lJ29JN7k v/taD3potgQiIRAxXbtANVtpkKhI7xx15RVT/2rtoTMnlryh4XHBFU7kgnnQLSzzvYwZw1yRoRM Z2+FvIDH0G/sltGpXzw3H9OHCL+XVl+OKzFFfbA3TiB17T6aF3h4XJQK7gaS8y4kREpDmqrDiac zrCnev3GwxvKH0GosOteu3P1Zl1suXZ2gyv1x63TuAhaSIWSBGtGes7UTLS5NlHwxMuAs6QRFaA Hye/wcT+d8mYq3/piHuPJ0yebO4XOwXXKZ8hyPV3GG4e+85zerqtI9s7I0rcmBnGuCHuprMNV/o uC2E4qCiMWegW/3RuRP96q8rzBb7IHGf/TynnlJonplTNrh4+0iF9Fbo+/LMLYLxjNsLgdPMhqT WFdCZvkYA== X-Received: by 2002:a05:6000:2489:b0:488:800b:11f3 with SMTP id ffacd0b85a97d-488800b1336mr6029083f8f.49.1790371000248; Fri, 25 Sep 2026 14:16:40 -0700 (PDT) Received: from localhost.localdomain ([2a0a:ef40:1479:f801:b8e7:18fe:7039:e475]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4887a34a570sm9271781f8f.13.2026.09.25.14.16.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 25 Sep 2026 14:16:39 -0700 (PDT) From: Mohamed Jouini To: Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, Mohamed Jouini Subject: [PATCH 2/3] platform/x86: uniwill-laptop: Add platform profile support Date: Fri, 25 Sep 2026 22:16:34 +0100 Message-ID: <20260925211635.1422516-3-amad3v@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260925211635.1422516-1-amad3v@gmail.com> References: <20260925211635.1422516-1-amad3v@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 #include #include +#include #include #include #include @@ -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