From 922f0e157063a5aa20da5410fced6a68edf1e12a Mon Sep 17 00:00:00 2001 From: James A DellaMorte Date: Sat, 1 Aug 2026 10:05:13 -0400 Subject: [PATCH] power: supply: macsmc: add "auto-discharge" charge behaviour for CHLS When lowering the charge-control end threshold on machines that use the CHLS key, the driver unconditionally sets CHLS_FORCE_DISCHARGE, actively draining the battery down to the limit even on AC power. Most laptop charge-limit implementations instead just cap charging at the threshold and let the battery drain naturally through use. Add a new "auto-discharge" charge_behaviour value that opts into the active discharge, and make end-threshold writes preserve the current CHLS_FORCE_DISCHARGE bit instead of forcing it on. "auto" keeps its documented meaning of only respecting the thresholds. The selection lives in the CHLS key itself, so no state is kept in the driver, it persists across reboots, and systems upgrading from the old behaviour keep force-discharge enabled until they explicitly write "auto". Resolves the existing TODO. Co-Authored-By: Claude Fable 5 Signed-off-by: James A DellaMorte --- Documentation/ABI/testing/sysfs-class-power | 2 + drivers/power/supply/macsmc-power.c | 48 ++++++++++++++++++--- drivers/power/supply/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 32697b926cc8c2..6b439c628b6ee1 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -513,6 +513,8 @@ Description: inhibit-charge: Do not charge while AC is attached inhibit-charge-awake: inhibit-charge only when device is awake force-discharge: Force discharge while AC is attached + auto-discharge: auto, but actively discharge down to + the charge control end threshold ===================== ======================================== What: /sys/class/power_supply//technology diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index cfc57eb9a471b0..0cbd747794710c 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -295,6 +295,7 @@ static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) int ret; u8 val8; u8 chte_buf[4]; + u16 vu16; if (power->has_ch0i) { ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8); @@ -319,6 +320,14 @@ static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; } + if (power->has_chls) { + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret) + return ret; + if (vu16 & CHLS_FORCE_DISCHARGE) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE; + } + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; } @@ -327,6 +336,10 @@ static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int v int ret; switch (val) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE: + if (!power->has_chls) + return -EOPNOTSUPP; + fallthrough; case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: /* Reset all inhibitors to a known-good 'auto' state */ if (power->has_ch0i) { @@ -344,6 +357,20 @@ static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int v if (ret) return ret; } + + /* Set or clear force-discharge to the CHLS charge limit */ + if (power->has_chls) { + u16 vu16; + + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret) + return ret; + if (val == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE) + vu16 |= CHLS_FORCE_DISCHARGE; + else + vu16 &= ~CHLS_FORCE_DISCHARGE; + return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), vu16); + } return 0; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: @@ -606,12 +633,18 @@ static int macsmc_battery_set_property(struct power_supply *psy, return 0; case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: if (power->has_chls) { - u16 kval = 0; - /* TODO: Make CHLS_FORCE_DISCHARGE configurable */ + u16 kval; + int ret; + + /* Preserve the force-discharge (auto-discharge) charge_behaviour */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &kval); + if (ret) + return ret; + kval &= CHLS_FORCE_DISCHARGE; if (val->intval < CHLS_MIN_END_THRESHOLD) - kval = CHLS_FORCE_DISCHARGE | CHLS_MIN_END_THRESHOLD; + kval |= CHLS_MIN_END_THRESHOLD; else if (val->intval < 100) - kval = CHLS_FORCE_DISCHARGE | (val->intval & 0xff); + kval |= val->intval & 0xff; return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), kval); } else if (power->has_chwa) { return apple_smc_write_flag(power->smc, SMC_KEY(CHWA), @@ -945,10 +978,13 @@ static int macsmc_power_probe(struct platform_device *pdev) } /* Detect charge limit method (CHWA vs CHLS) */ - if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) + if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) { power->has_chwa = true; - else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) + } else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) { power->has_chls = true; + power->batt_desc.charge_behaviours |= + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE); + } if (power->has_chwa || power->has_chls) { props[nprops++] = POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index f30a7b9ccd5e93..d22541f4a4ecf6 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -146,6 +146,7 @@ static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = { [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake", [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE] = "auto-discharge", }; static struct power_supply_attr power_supply_attrs[] __ro_after_init = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a01d..82215a75385010 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -221,6 +221,7 @@ enum power_supply_charge_behaviour { POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE, POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE, POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE, + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO_DISCHARGE, }; enum power_supply_notifier_events {