From nobody Mon Jun 8 08:30:20 2026 Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.171]) (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 D81023148B5 for ; Sun, 31 May 2026 09:31:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780219910; cv=none; b=KX5HwJAVyzZWJtL5oOW7+w0NgjID8w/k5XvCF35PYTOgEDAO8gpOfkCTKJUpdObvtIkDjC6p+L5UEVpKTwNOhneivjJu6H4PwVWKXymivchsz/yVZl0FaB1396l92cngm6x5agfRKmTn+eP7HKyDxcCAW77WzUJsD5x03hyA9V4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780219910; c=relaxed/simple; bh=34bWtV7yPXNwTHhyd4tidM02gLkCJMg/I4D39lMzq4I=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=CIi7k3bg7iBMG2q7eTOuo+dVtFXshjenZzUKrF3FC++RKDlhGk+kuYshFeKGjmtN8JZc6JfWPE5+NEepybfIBRRquXJL47cq9vhrHQeuP4hRr818wZclgYkwOqXWbJMnY+W8Wa/3muxMOKtTqsvcDRGzDpaU5iYJ4DxQ6WjVzYA= 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=NHaA6GBf; arc=none smtp.client-ip=209.85.214.171 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="NHaA6GBf" Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-2bf18c30bb2so17979605ad.0 for ; Sun, 31 May 2026 02:31:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780219908; x=1780824708; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=yw0twq5qwLkcmJ7duol9vBU9e4SryySAWv6YUQ76/B8=; b=NHaA6GBfF/RA17VCB6VIi8JYu9vN5D+Lq325dQZVj3C/3tZoVwyKJGbOSfHgbBNiFG 28ywFsSxjdDh/s2H2hS45ggOA3iYW7DiQ8XLIqnJ6d0lMBuCSOYONhFcZAjrlAP9n54z DPO2qodhQfww004dOYs6JlNbpeFMYDjMdbHa6BbcbRNvSZTOQRdxt2ZKSODK7WjdRXn/ fuJf5aN+VkfvYVMoPn5iNuOaC85yhQK3g1RgJ/h7WqZuFIF5nETazr5EK0poXlMuqD0h ui7cM70e6IKCCN4QZ60Q5AoDxlWAcbFOdDzAUqo4ydxY5YJGKHN48XQnQ0SvizXngg8v pu+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780219908; x=1780824708; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=yw0twq5qwLkcmJ7duol9vBU9e4SryySAWv6YUQ76/B8=; b=JriS7qDm0V2ArS6vNni2P48QUcMs4IgT4gLiVMNVTle5Io/MYeQ1Sm6DoQJDL/Dc+p jIB8n3+qg8hZykiqu6HP6ZX21AoOgvzvabrAaJlJbj4ioAuE/UGSxEvy4KGadBgIVuyu 6wSPFCC4sZRXbJzAGqBsLWeuG7GV7dYxfjbcB6eFZj+72gMNltslUTvpbuOc75h8iVnZ UIAAjXadXOct1FjeCpwDOAhtC5C9XbdRKhUMWopzkfJTsuM8wUZKfD4NSTIvqSOse1Tn ykyOqfxrSUkd/ZfgaRUZcXLzsY6yf6D1ijLF71WyVeO+UEN+g4c5fgWb9qTkoZPYXq64 orHQ== X-Forwarded-Encrypted: i=1; AFNElJ/15KkV9Ngw1eRNb0kDpyLROaSe3lRiboCojBiiHJLP/pkn2TbZOyaVAWl6M+8dIBitK9q6ibfz8kGMoQk=@vger.kernel.org X-Gm-Message-State: AOJu0YwWXwwbCPUsZ8Zh1+l9fS3qzx1GsQvfGxyiktM3VmSvhVQKEZOd /IRn06Yjanhhvb3h1rE1FAP+Kcm01KdqAzJIZIsrbNy0JVC5DH0pECFI X-Gm-Gg: Acq92OFIw6gH6Zyv+etmLUSUVXZYj+L9e6dfjFKDoHTNoWc+MQ6uCncOaaxUElP0rUD Ola1JYMmr/RtFZ7yOYa3p4UbUzbAa3TGGlU/ElkeS+4AEWMsPyl5LIuQD/FTL7YD0ZKmNl8ZwI+ zh67P64Er6LMyehU29i2rLNCrotP5/UH0H6LEgVT9tia8XZeEVFse4FUzW/ymfpaIKBUcf8Rq2l aRztDq+UK85IxHNCBCXPAoRtri09dCmOtGIXuKlTP1kw+o4vtMUK1WdhfTdBqIGCKhPy8U0QqUD V3m6t/xUQgiJjz5QneMNutkE/9N2Xy4ZZ9QWpvtno8Q/LsBDvvH+sZSoKMjge6YCyBUrnhwDWpq c+WGj0VUYcu8F2//M8BhS4vaIKzs3xPWt1TVQuahMdNYMsNoqutjvQRQkqwJr/TM0Oeu8+b3JG+ sTg9G5tjbPPpOnk3SXTjM= X-Received: by 2002:a17:903:1a0f:b0:2bf:195d:21de with SMTP id d9443c01a7336-2bf36795233mr83811235ad.7.1780219908072; Sun, 31 May 2026 02:31:48 -0700 (PDT) Received: from qby-laptop ([2a13:edc0:18:16f::a]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2bf23915adcsm68003995ad.0.2026.05.31.02.31.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 31 May 2026 02:31:47 -0700 (PDT) From: Mingyou Chen To: Armin Wolf , Jonathan Corbet , Shuah Khan , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= , Mingyou Chen , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1] platform/x86: bitland-mifs-wmi: Add thermal cooling device fan control Date: Sun, 31 May 2026 17:30:50 +0800 Message-ID: <20260531093101.30303-1-qby140326@gmail.com> X-Mailer: git-send-email 2.54.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Register the onboard laptop fans within the Linux thermal framework as cooling devices to enable explicit thermal policy management. This implementation exposes two separate fan loops: - "bitland_main_fan" - "bitland_sys_fan" The cooling devices support states from 0 to 100: - State 0 preserves or restores autonomous EC-driven fan curve control. - States 1 to 100 transition the loop into manual mode, where the state is scaled linearly between a profile-defined minimum and maximum hardware duty cycle boundary. Because safe operational constraints vary depending on the platform's current state and hardware configuration, the driver queries the active WMI platform profile at runtime and branches on the CPU vendor (boot_cpu_data.x86_vendor) to enforce distinct Intel or AMD fan speed safety ranges. Managed device lifecycle support is handled via devm_add_action_or_reset() to ensure that the cooling devices are cleanly unregistered and the fans are returned safely to hardware-controlled automatic mode upon driver unbind. Assisted-by: Gemini:gemini-3.5-flash Signed-off-by: Mingyou Chen --- .../wmi/devices/bitland-mifs-wmi.rst | 21 +- drivers/platform/x86/bitland-mifs-wmi.c | 196 ++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) diff --git a/Documentation/wmi/devices/bitland-mifs-wmi.rst b/Documentation= /wmi/devices/bitland-mifs-wmi.rst index 9e86ecc2993c..5e5a51677883 100644 --- a/Documentation/wmi/devices/bitland-mifs-wmi.rst +++ b/Documentation/wmi/devices/bitland-mifs-wmi.rst @@ -201,7 +201,26 @@ The ``GPUMode`` (0x09) allows switching between Hybrid= (Muxless) and Discrete take effect in the BIOS/Firmware. =20 Fan Control ------------ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + The system supports both automatic EC control and manual overrides. Comman= d ID 0x14 (``MaxFanSpeedSwitch``) is used to toggle manual control, while ID 0x= 15 sets the actual PWM duty cycle. + +The driver exposes the two onboard fan loops to the Linux thermal framewor= k as +standard cooling devices: + +- ``bitland_main_fan``: Controls the combined CPU/GPU cooling loop. +- ``bitland_sys_fan``: Controls the auxiliary chassis/system fan loop. + +Thermal framework cooling states range from 0 to 100 and map to hardware o= perations: + +- **State 0**: Restores autonomous EC fan curve control (Automatic mode). +- **State 1-100**: Switches the loop to Manual mode, scaling the target ha= rdware + speed linearly between the profile's safe minimum and maximum bounds via: + ``target_speed =3D min_val + ((state - 1) * (max_val - min_val) / 99)`` + +The valid hardware boundaries (``min_val`` and ``max_val``) are evaluated +dynamically at runtime during state changes. The driver checks both the cu= rrently +active platform profile and the processor architecture (Intel vs. AMD via = CPU +vendor matching) to enforce the correct operational safety envelopes. diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86= /bitland-mifs-wmi.c index b0d06a80e89e..4fb94046d5bf 100644 --- a/drivers/platform/x86/bitland-mifs-wmi.c +++ b/drivers/platform/x86/bitland-mifs-wmi.c @@ -26,10 +26,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -38,6 +40,9 @@ #define BITLAND_MIFS_GUID "B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B" #define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" =20 +#define BITLAND_FAN_CONTROL_AUTO 0 +#define BITLAND_FAN_CONTROL_MANUAL 1 + enum bitland_mifs_operation { WMI_METHOD_GET =3D 250, WMI_METHOD_SET =3D 251, @@ -150,6 +155,60 @@ struct bitland_fan_notify_data { u16 speed; }; =20 +enum bitland_fan_type { + BITLAND_FAN_CPU_GPU =3D 0, + BITLAND_FAN_SYS =3D 1, + BITLAND_FAN_MAX, +}; + +struct bitland_fan_range { + u8 min; + u8 max; +}; + +struct bitland_cooling_dev { + struct bitland_mifs_wmi_data *data; + struct thermal_cooling_device *cdev; + enum bitland_fan_type type; + u8 cur_val; +}; + +/* Fan safety bounds indexed by [WMI_PP_MODE][FAN_TYPE] */ +static const struct bitland_fan_range intel_fan_ranges[4][BITLAND_FAN_MAX]= =3D { + [WMI_PP_BALANCED] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 26, 35 }, [BITLAND_FAN_SYS] =3D { 59, 69 }, + }, + [WMI_PP_PERFORMANCE] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 32, 38 }, [BITLAND_FAN_SYS] =3D { 70, 80 }, + }, + [WMI_PP_QUIET] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 19, 29 }, [BITLAND_FAN_SYS] =3D { 25, 64 }, + }, + [WMI_PP_FULL_SPEED] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 40, 44 }, [BITLAND_FAN_SYS] =3D { 75, 82 }, + }, +}; + +static const struct bitland_fan_range amd_fan_ranges[4][BITLAND_FAN_MAX] = =3D { + [WMI_PP_BALANCED] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 26, 35 }, [BITLAND_FAN_SYS] =3D { 59, 69 }, + }, + [WMI_PP_PERFORMANCE] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 32, 38 }, [BITLAND_FAN_SYS] =3D { 64, 72 }, + }, + [WMI_PP_QUIET] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 19, 29 }, [BITLAND_FAN_SYS] =3D { 17, 64 }, + }, + [WMI_PP_FULL_SPEED] =3D { + [BITLAND_FAN_CPU_GPU] =3D { 40, 44 }, [BITLAND_FAN_SYS] =3D { 75, 82 }, + }, +}; + +static const char *const cooling_dev_labels[BITLAND_FAN_MAX] =3D { + [BITLAND_FAN_CPU_GPU] =3D "bitland_main_fan", + [BITLAND_FAN_SYS] =3D "bitland_sys_fan", +}; + struct bitland_mifs_wmi_data { struct wmi_device *wdev; struct mutex lock; /* Protects WMI calls */ @@ -158,6 +217,7 @@ struct bitland_mifs_wmi_data { struct input_dev *input_dev; struct device *hwmon_dev; struct device *pp_dev; + struct bitland_cooling_dev cooling_devs[BITLAND_FAN_MAX]; enum platform_profile_option saved_profile; }; =20 @@ -184,6 +244,25 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_w= mi_data *data, return 0; } =20 +static int bitland_get_wmi_profile(struct bitland_mifs_wmi_data *data) +{ + struct bitland_mifs_input input =3D { + .operation =3D WMI_METHOD_GET, + .function =3D WMI_FN_SYSTEM_PER_MODE, + }; + struct bitland_mifs_output result; + int ret; + + ret =3D bitland_mifs_wmi_call(data, &input, &result); + if (ret) + return ret; + + if (result.data[0] >=3D 4) + return -EPROTO; + + return result.data[0]; +} + static int laptop_profile_get(struct device *dev, enum platform_profile_option *profile) { @@ -406,6 +485,106 @@ static const struct hwmon_chip_info laptop_chip_info = =3D { .info =3D laptop_hwmon_info, }; =20 +static int bitland_set_fan_hardware(struct bitland_cooling_dev *fan, bool = manual, u8 speed) +{ + int ret; + + struct bitland_mifs_input switch_input =3D { + .operation =3D WMI_METHOD_SET, + .function =3D WMI_FN_MAX_FAN_SWITCH, + .payload =3D { + [0] =3D (u8)fan->type, + [1] =3D manual ? BITLAND_FAN_CONTROL_MANUAL : BITLAND_FAN_CONTROL_AUTO, + }, + }; + + ret =3D bitland_mifs_wmi_call(fan->data, &switch_input, NULL); + if (ret) + return ret; + + if (!manual) + return 0; + + struct bitland_mifs_input speed_input =3D { + .operation =3D WMI_METHOD_SET, + .function =3D WMI_FN_MAX_FAN_SPEED, + .payload =3D { + [0] =3D (u8)fan->type, + [1] =3D speed, + }, + }; + + return bitland_mifs_wmi_call(fan->data, &speed_input, NULL); +} + +static int bitland_cooling_get_max_state(struct thermal_cooling_device *cd= ev, + unsigned long *state) +{ + *state =3D 100; + return 0; +} + +static int bitland_cooling_get_cur_state(struct thermal_cooling_device *cd= ev, + unsigned long *state) +{ + struct bitland_cooling_dev *fan =3D cdev->devdata; + + *state =3D fan->cur_val; + return 0; +} + +static int bitland_cooling_set_cur_state(struct thermal_cooling_device *cd= ev, + unsigned long state) +{ + struct bitland_cooling_dev *fan =3D cdev->devdata; + struct bitland_mifs_wmi_data *data =3D fan->data; + const struct bitland_fan_range *range; + int profile_mode; + u8 target_speed; + int ret; + + if (state > 100) + return -EINVAL; + + if (state =3D=3D 0) { + ret =3D bitland_set_fan_hardware(fan, false, 0); + } else { + profile_mode =3D bitland_get_wmi_profile(data); + if (profile_mode < 0) + return profile_mode; + + if (boot_cpu_data.x86_vendor =3D=3D X86_VENDOR_AMD) + range =3D &amd_fan_ranges[profile_mode][fan->type]; + else + range =3D &intel_fan_ranges[profile_mode][fan->type]; + + /* Scale 1..100 map directly to current profile's safe min..max min-boun= d */ + target_speed =3D range->min + ((state - 1) * (range->max - range->min) /= 99); + ret =3D bitland_set_fan_hardware(fan, true, target_speed); + } + + if (ret =3D=3D 0) + fan->cur_val =3D state; + + return ret; +} + +static const struct thermal_cooling_device_ops bitland_cooling_ops =3D { + .get_max_state =3D bitland_cooling_get_max_state, + .get_cur_state =3D bitland_cooling_get_cur_state, + .set_cur_state =3D bitland_cooling_set_cur_state, +}; + +static void bitland_thermal_unregister_action(void *data) +{ + struct bitland_cooling_dev *fan =3D data; + + if (fan->cdev) { + bitland_set_fan_hardware(fan, false, 0); + thermal_cooling_device_unregister(fan->cdev); + } +} + static int laptop_kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) { @@ -708,6 +887,23 @@ static int bitland_mifs_wmi_probe(struct wmi_device *w= dev, const void *context) if (IS_ERR(drv_data->hwmon_dev)) return PTR_ERR(drv_data->hwmon_dev); =20 + for (int i =3D 0; i < BITLAND_FAN_MAX; i++) { + struct bitland_cooling_dev *fan =3D &drv_data->cooling_devs[i]; + + fan->data =3D drv_data; + fan->type =3D i; + fan->cur_val =3D 0; + + fan->cdev =3D thermal_cooling_device_register(cooling_dev_labels[i], + fan, &bitland_cooling_ops); + if (IS_ERR(fan->cdev)) + return PTR_ERR(fan->cdev); + + ret =3D devm_add_action_or_reset(&wdev->dev, bitland_thermal_unregister_= action, fan); + if (ret) + return ret; + } + /* Register keyboard LED */ drv_data->kbd_led.max_brightness =3D 3; drv_data->kbd_led.brightness_set_blocking =3D laptop_kbd_led_set; base-commit: 174914ea551314c52a61713b9c4bde9e42d48073 --=20 2.54.0