From nobody Mon Jun 8 07:22:53 2026 Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) (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 234D2392C42 for ; Sun, 31 May 2026 18:13:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251224; cv=none; b=XfWMy3jp9sQIe/OzdxEcB1apnzJlAyCKSWzonreNNliAM60LU4Z00nweESWZ32gdI75T3/k9LUzAvpotwysZ5XOAF8BjiYSEcOrhLJ0kel8/ONwjtHeHUp2IxNGeGN3yDhaZ8MGRxAEUZZYGNxVzI9emhHChjc1Tw2ua8rRZ7G8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251224; c=relaxed/simple; bh=sdJ+FYN+5u3FKoq0bsozzCIYAPQJjewWKODeLqMHW3w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=a63Xlm2PRWxHRjAtfM6a1odTprw4KTXen1vqLC7JmRHhS6D28femKSE4SL3ZMYZyRe+xt5zBCmhbYN8YcywM3/EO1/TZdipHgae29Zd+pIJTHdOjjy1bStVXvIyJEzrZw1tyT/a+YqwDmjvvUyaoJRFOQUhz0TZgz2K+GSpYM9k= 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=XPu3EMlk; arc=none smtp.client-ip=209.85.214.180 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="XPu3EMlk" Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-2c0b7ca8831so2175705ad.1 for ; Sun, 31 May 2026 11:13:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780251221; x=1780856021; 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; bh=Zq+iuii3TV8UNuKcPAeXq3DMOEeF5c38TvhCpVD+Lxw=; b=XPu3EMlkWTtNvtwX2iBxdLf88yfpuLPmi4ky5b+v4kuIfmkytk9Es1iqkWHMgsOyvP GF5bh4sBKYxQkODsW7jLO8ecHP3964kMZlQXSkc5f0Rwd42yw01Bd90qPhdSTQxZnwQr JjxGYSLoYitlpoMvVAqrA58NAB0bUE+dqfUIf3x057SE9IkvXf75Fq4vAnOlosr4BZdp z6/rfS1ciTGP8GyXSWHGYCjxNKvqkborHpbcx+I3SjiKkcN1ptH+funLJXFk5LgsJdBr OihpdadKQUY44vkeOlKltXAO+mg3PsvGr5FVay2h1py8GaQezEm8xaLM4HWGxugcycW0 PJDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780251221; x=1780856021; 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; bh=Zq+iuii3TV8UNuKcPAeXq3DMOEeF5c38TvhCpVD+Lxw=; b=mytSPCo6uLGN6DQUTigijH3H0S5gcyMjxh+oASEj1G/GKP83jVX3L7AL24uAWQInC6 qu9jGjHLqLF2KkGpq2UNcpSwzw85enuJaOgLl4cq8OGfNWqNPpSixUs6mghvvu8Qmpio uPWKjTFnZ1e7FcIazQRCdbFqPyIyx1/AvGbBADhJsQxIr4K1+XGKyW5N8pEJ8kjpEpfK W7F67R0H5s8Z93CoQ1OhkuZZFBhQ0Yb1VG4eiQBcvwmZ0WyzaHUF1tCwJWmqiGOJkM5J 3LYhH1fFR4SXe/QhK3LfQsxPMRhXOCsLVshWIQe+CfsG+JZpySFRk53K//7do9PTTxbV o+vA== X-Forwarded-Encrypted: i=1; AFNElJ+a7GDob75f5SAv4Ll6V5qlY7s2eN2obskZBqIyw7CKFjm7Cx+/WjpwWvjftHwoMhCeWgGcmNZ3WJESsEw=@vger.kernel.org X-Gm-Message-State: AOJu0YypwznHMhxOL1rPfQR/JyFCPtmZ4KXdPZ7xWnvIWquzjKb75qYO D4hk00ATwSkuLx/ZRkasi7ADwOqwaEjQ33LqNvvsvrF1h7YmIB4nzAB9mz+v9IFo X-Gm-Gg: Acq92OFDmTjFnAENCydbABejDRpm5wp9gAKoKrv22ESxo0Kj5Ly4jb8vhme1yqaN2Ou 5jbedY/n6ZF5egC0Iq7XmZj9Z7kX869Ibs+dVcOoH6qQtNkH8C/24IZ+Iq+UYNbJehqXiH430Ae POpbyulj2cPL4NueeYG0GDCddSqp1DbNYxwsfbqyAg9bHWeAbCTBg+FYHao6sC0X1S+Pz+8Fy+q p6cG/jYG6N9X5MjPgZsPeF/wvjsN3AiwIXITljKr5vOz8ebAY/LMN8rJ1J+Anc6QTfGbOe422in nx6moPyPJbfHPcX1ep3wIsFjWFH8inbKqF8e540StijAQzUC0y8Rjn5CZ/8GR6OhBKZXte81Rv0 acQlxRdJumefLNojzUO3un57hk6MRaol2e36UK4OFr/OAAYh0N/83I4loSeiO96jtvZd96O92VZ eOAjep/L5GD82tHCFltxdeyvQzLvPhHQ2WzP47CgFI X-Received: by 2002:a17:902:e54c:b0:2bd:c5fa:a78 with SMTP id d9443c01a7336-2bf3688a2cfmr53623945ad.8.1780251221261; Sun, 31 May 2026 11:13:41 -0700 (PDT) Received: from magniquick ([2409:40f3:100e:de5:ae3b:b2d0:4f43:7ae]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2bf23c3f496sm83256775ad.76.2026.05.31.11.13.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 31 May 2026 11:13:40 -0700 (PDT) From: Navon John Lukose To: hansg@kernel.org, ilpo.jarvinen@linux.intel.com Cc: sre@kernel.org, platform-driver-x86@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH v2] platform/x86: hp-wmi: Add charge behaviour support Date: Sun, 31 May 2026 23:43:26 +0530 Message-ID: <20260531181326.1000801-1-navonjohnlukose@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260505200222.157144-1-navonjohnlukose@gmail.com> References: <20260505200222.157144-1-navonjohnlukose@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: quoted-printable Content-Type: text/plain; charset="utf-8" Some HP laptops expose battery charge mode control through the existing HP BIOS WMI GUID. On HP board 85C6, command types 0x1f and 0x2b map to the ACPI GBCC/GBCO and SBCC/SBCO methods through the WMI command dispatcher. Expose the mode control through the standard power_supply charge_behaviour property using a battery hook and power_supply extension. The initial quirk is limited to board 85C6 and still requires a successful charge mode read before registering the extension. The firmware uses different commands for restoring auto mode and setting explicit charge modes, so report the last successfully requested policy after seeding it from firmware at probe time. The regular battery status continues to report the live charging state. Compile the battery hook support only when the ACPI battery driver is reachable so non-battery hp-wmi configurations keep building. Assisted-by: Codex:GPT-5.5 Signed-off-by: Navon John Lukose --- Changes in v2: - Compile the battery hook integration only when ACPI battery support is reachable. - Keep charge_behaviour as the last successfully requested policy. - Use FIELD_PREP() for the write-mode field and document the readback value= s. - Leave the ACPI battery header include unguarded and split compact return handling into explicit branches. - Reject unknown GBCO readback values during setup instead of treating them= as auto. drivers/platform/x86/hp/hp-wmi.c | 230 +++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-= wmi.c index d1cc6e7d176c..66f409962cd9 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -14,6 +14,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt =20 #include +#include #include #include #include @@ -36,6 +37,8 @@ #include #include =20 +#include + MODULE_AUTHOR("Matthew Garrett "); MODULE_DESCRIPTION("HP laptop WMI driver"); MODULE_LICENSE("GPL"); @@ -309,7 +312,9 @@ enum hp_wmi_commandtype { HPWMI_HOTKEY_QUERY =3D 0x0c, HPWMI_FEATURE2_QUERY =3D 0x0d, HPWMI_WIRELESS2_QUERY =3D 0x1b, + HPWMI_BATTERY_CHARGE_CONTROL =3D 0x1f, HPWMI_POSTCODEERROR_QUERY =3D 0x2a, + HPWMI_BATTERY_CHARGE_MODE =3D 0x2b, HPWMI_SYSTEM_DEVICE_MODE =3D 0x40, HPWMI_THERMAL_PROFILE_QUERY =3D 0x4c, }; @@ -378,6 +383,27 @@ enum hp_wireless2_bits { #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) !=3D HPWMI_POWER_FW_OR= _HW) #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) =20 +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +#define HPWMI_CHARGE_BEHAVIOURS \ + (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) + +/* + * SBCC/SBCO read the requested mode from the high byte of the input value. + * GBCO returns literal status values, not a bit mask. + */ +#define HPWMI_CHARGE_MODE_MASK GENMASK(15, 8) +#define HPWMI_CHARGE_MODE_AUTO 0x00 +#define HPWMI_CHARGE_MODE_FORCE_DISCHARGE 0x02 +#define HPWMI_CHARGE_MODE_INHIBIT 0x05 + +#define HPWMI_CHARGE_MODE_READ_AUTO 0x00 +#define HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE 0x02 +#define HPWMI_CHARGE_MODE_READ_INHIBIT 0x04 +#define HPWMI_CHARGE_MODE_READ_INHIBIT_EXT 0x06 +#endif + struct bios_rfkill2_device_state { u8 radio_type; u8 bus_type; @@ -465,6 +491,18 @@ static const char * const tablet_chassis_types[] =3D { "32" /* Detachable */ }; =20 +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +static const struct dmi_system_id hp_wmi_charge_control_quirks[] __initcon= st =3D { + { + .matches =3D { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "85C6"), + }, + }, + {} +}; +#endif + #define DEVICE_MODE_TABLET 0x06 =20 #define CPU_FAN 0 @@ -694,6 +732,194 @@ static int hp_wmi_read_int(int query) return val; } =20 +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +static DEFINE_MUTEX(hp_wmi_charge_lock); +static enum power_supply_charge_behaviour hp_wmi_charge_behaviour =3D + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + +static int hp_wmi_charge_mode_to_behaviour(int mode, + enum power_supply_charge_behaviour *behaviour) +{ + switch (mode) { + case HPWMI_CHARGE_MODE_READ_AUTO: + *behaviour =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + return 0; + case HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE: + *behaviour =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + return 0; + case HPWMI_CHARGE_MODE_READ_INHIBIT: + case HPWMI_CHARGE_MODE_READ_INHIBIT_EXT: + *behaviour =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + return 0; + default: + return -EINVAL; + } +} + +static int hp_wmi_charge_mode_read(void) +{ + int val =3D 0, ret; + + ret =3D hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_READ, + &val, zero_if_sup(val), sizeof(val)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return val & 0xff; +} + +static int hp_wmi_charge_write(enum hp_wmi_commandtype query, u32 mode) +{ + int ret; + + mode =3D FIELD_PREP(HPWMI_CHARGE_MODE_MASK, mode); + ret =3D hp_wmi_perform_query(query, HPWMI_WRITE, &mode, sizeof(mode), 0); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return 0; +} + +static int hp_wmi_charge_set_behaviour(enum power_supply_charge_behaviour = behaviour) +{ + int ret; + + guard(mutex)(&hp_wmi_charge_lock); + + /* + * HP firmware exposes normal charging through the GBCC/SBCC command + * pair, while explicit charge modes use the GBCO/SBCO command pair. + */ + switch (behaviour) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + ret =3D hp_wmi_charge_write(HPWMI_BATTERY_CHARGE_CONTROL, + HPWMI_CHARGE_MODE_AUTO); + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + ret =3D hp_wmi_charge_write(HPWMI_BATTERY_CHARGE_MODE, + HPWMI_CHARGE_MODE_INHIBIT); + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + ret =3D hp_wmi_charge_write(HPWMI_BATTERY_CHARGE_MODE, + HPWMI_CHARGE_MODE_FORCE_DISCHARGE); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + /* + * charge_behaviour is the requested policy. The live charging state is + * still reported by the regular battery status property. + */ + hp_wmi_charge_behaviour =3D behaviour; + + return 0; +} + +static int hp_wmi_charge_get_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + if (psp !=3D POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) + return -EINVAL; + + guard(mutex)(&hp_wmi_charge_lock); + val->intval =3D hp_wmi_charge_behaviour; + + return 0; +} + +static int hp_wmi_charge_set_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + if (psp !=3D POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) + return -EINVAL; + + return hp_wmi_charge_set_behaviour(val->intval); +} + +static int hp_wmi_charge_property_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return psp =3D=3D POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR; +} + +static const enum power_supply_property hp_wmi_charge_properties[] =3D { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +static const struct power_supply_ext hp_wmi_charge_extension =3D { + .name =3D "hp-wmi-charge-control", + .properties =3D hp_wmi_charge_properties, + .num_properties =3D ARRAY_SIZE(hp_wmi_charge_properties), + .charge_behaviours =3D HPWMI_CHARGE_BEHAVIOURS, + .get_property =3D hp_wmi_charge_get_property, + .set_property =3D hp_wmi_charge_set_property, + .property_is_writeable =3D hp_wmi_charge_property_is_writeable, +}; + +static int hp_wmi_charge_add_battery(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + return power_supply_register_extension(battery, &hp_wmi_charge_extension, + &hp_wmi_platform_dev->dev, NULL); +} + +static int hp_wmi_charge_remove_battery(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &hp_wmi_charge_extension); + return 0; +} + +static struct acpi_battery_hook hp_wmi_charge_battery_hook =3D { + .name =3D "HP WMI charge control", + .add_battery =3D hp_wmi_charge_add_battery, + .remove_battery =3D hp_wmi_charge_remove_battery, +}; + +static int __init hp_wmi_charge_control_setup(struct device *dev) +{ + enum power_supply_charge_behaviour behaviour; + int ret; + + if (!dmi_check_system(hp_wmi_charge_control_quirks)) + return 0; + + ret =3D hp_wmi_charge_mode_read(); + if (ret < 0) + return 0; + + ret =3D hp_wmi_charge_mode_to_behaviour(ret, &behaviour); + if (ret) + return 0; + + scoped_guard(mutex, &hp_wmi_charge_lock) + hp_wmi_charge_behaviour =3D behaviour; + + return devm_battery_hook_register(dev, &hp_wmi_charge_battery_hook); +} +#else +static int __init hp_wmi_charge_control_setup(struct device *dev) +{ + return 0; +} +#endif + static int hp_wmi_get_dock_state(void) { int state =3D hp_wmi_read_int(HPWMI_HARDWARE_QUERY); @@ -2284,6 +2510,10 @@ static int __init hp_wmi_bios_setup(struct platform_= device *device) if (err < 0) return err; =20 + err =3D hp_wmi_charge_control_setup(&device->dev); + if (err) + return err; + thermal_profile_setup(device); =20 return 0; --=20 2.54.0