* [PATCH v2 0/2] hwmon: (applesmc) add charge_control_end_threshold support
@ 2026-09-18 17:50 Jordan Brough
2026-09-18 17:50 ` [PATCH v2 1/2] ACPI: battery: add unified battery hook mechanism for ACPI and SBS batteries Jordan Brough
2026-09-18 17:50 ` [PATCH v2 2/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
0 siblings, 2 replies; 3+ messages in thread
From: Jordan Brough @ 2026-09-18 17:50 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Rafael J . Wysocki
Cc: Thomas Weißschuh, Henrik Rydberg, linux-hwmon, linux-acpi,
linux-kernel, Jordan Brough
This patch series adds battery charge threshold control support to the
applesmc driver for Intel-based MacBooks, exposing the standard
charge_control_end_threshold sysfs attribute on the battery power supply.
In v1, separate SBS battery hooking functions were introduced in sbs.c.
Following reviewer feedback from Thomas Weißschuh, this version unifies
the ACPI battery hooking mechanism across both ACPI Control Method
batteries (drivers/acpi/battery.c) and ACPI Smart Battery Systems
(drivers/acpi/sbs.c) into a shared helper in drivers/acpi/battery_hook.c
built directly into the ACPI core (acpi.o).
The public consumer API signatures (battery_hook_register(),
battery_hook_unregister(), devm_battery_hook_register()) remain unchanged,
so existing drivers require no modifications while gaining the ability
to hook SBS batteries where present.
Patch 1 extracts the shared hook mechanism into
drivers/acpi/battery_hook.c and integrates both drivers/acpi/battery.c
and drivers/acpi/sbs.c.
Patch 2 adds charge_control_end_threshold support to
drivers/hwmon/applesmc.c with mutex synchronization around SMC register
writes (BCLM and BFCL) and error rollback on extension registration
failure.
Testing:
- Validated on Intel MacBook Air (MacBookAir6,2) with ACPI SBS battery
(BAT0): verified sysfs threshold read/write, KDE Powerdevil UI
detection, bounds checking, and hardware MagSafe LED transition between
amber and green.
- Validated on Lenovo IdeaPad with ACPI Control Method battery (BAT1)
to check that existing in-tree hook consumers (such as ideapad_laptop
conservation mode) continue operating as expected.
Changes in v2:
- Unified battery hook infrastructure into drivers/acpi/battery_hook.c
built into acpi.o, eliminating duplicate hook logic in sbs.c
(Thomas Weißschuh).
- Kept public consumer API signatures unchanged
(battery_hook_register/unregister, devm_battery_hook_register).
- Added applesmc_bat_lock mutex to serialize BCLM and BFCL SMC key writes,
preventing concurrent write desynchronization.
- Added mutex protection and error rollback in applesmc_battery_add()
to ensure applesmc_hooked_battery is safely handled if extension
registration fails.
- Added mutex protection for applesmc_bat_get_property() / set_property().
- Note: v1 was acked by Rafael J. Wysocki; the Acked-by tag was dropped
from Patch 1 in v2 due to the architectural refactoring into
battery_hook.c.
Link: https://lore.kernel.org/r/20260913231410.416922-1-jordan@brough.org [v1]
Jordan Brough (2):
ACPI: battery: add unified battery hook mechanism for ACPI and SBS
batteries
hwmon: (applesmc) add charge_control_end_threshold support
drivers/acpi/Makefile | 1 +
drivers/acpi/battery.c | 165 +-----------------------------------
drivers/acpi/battery_hook.c | 112 ++++++++++++++++++++++++
drivers/acpi/sbs.c | 8 +-
drivers/hwmon/applesmc.c | 157 ++++++++++++++++++++++++++++++++--
include/acpi/battery.h | 10 +++
6 files changed, 285 insertions(+), 168 deletions(-)
create mode 100644 drivers/acpi/battery_hook.c
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/2] ACPI: battery: add unified battery hook mechanism for ACPI and SBS batteries
2026-09-18 17:50 [PATCH v2 0/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
@ 2026-09-18 17:50 ` Jordan Brough
2026-09-18 17:50 ` [PATCH v2 2/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
1 sibling, 0 replies; 3+ messages in thread
From: Jordan Brough @ 2026-09-18 17:50 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Rafael J . Wysocki
Cc: Thomas Weißschuh, Henrik Rydberg, linux-hwmon, linux-acpi,
linux-kernel, Jordan Brough
drivers/acpi/battery.c provides a battery_hook_register()/unregister()
mechanism that lets other drivers (e.g. platform and hwmon drivers
exposing vendor-specific charge control) attach extra power_supply
properties to an ACPI Control Method Battery (HID PNP0C0A).
Some machines instead expose their battery through the ACPI Smart
Battery System (HID ACPI0002, "SBS") driver in drivers/acpi/sbs.c,
which has had no equivalent hook point.
Extract the battery hooking implementation out of drivers/acpi/battery.c
into a shared helper in drivers/acpi/battery_hook.c, built directly into
the ACPI core (acpi.o). Both drivers/acpi/battery.c and drivers/acpi/sbs.c
now register their power_supply devices with this shared hook
infrastructure via battery_hook_add_battery() and
battery_hook_remove_battery().
This unifies the hooking interface across both battery drivers so
callers only need to register a single struct acpi_battery_hook without
needing to know or care whether the underlying hardware exposes a Control
Method Battery or an SBS battery.
Suggested-by: Thomas Weißschuh <linux@weissschuh.net>
Signed-off-by: Jordan Brough <jordan@brough.org>
---
drivers/acpi/Makefile | 1 +
drivers/acpi/battery.c | 165 +-----------------------------------
drivers/acpi/battery_hook.c | 112 ++++++++++++++++++++++++
drivers/acpi/sbs.c | 8 +-
include/acpi/battery.h | 10 +++
5 files changed, 133 insertions(+), 163 deletions(-)
create mode 100644 drivers/acpi/battery_hook.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index d1b0affb844..57413f2a09e 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_ACPI) += acpi.o \
# All the builtin files are in the "acpi." module_param namespace.
acpi-y += osi.o osl.o utils.o reboot.o
acpi-y += nvs.o
+acpi-y += battery_hook.o
# Power management related files
acpi-y += wakeup.o
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 670853ec3a4..9b6af528bfe 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -105,7 +105,7 @@ struct acpi_battery {
struct kfifo acpi_notif_fifo;
struct delayed_work acpi_notif_dwork;
struct notifier_block pm_nb;
- struct list_head list;
+ struct acpi_battery_hook_device hook_dev;
unsigned long flags;
struct mutex property_lock; /* Protects properties below. */
@@ -744,164 +744,6 @@ static struct attribute *acpi_battery_attrs[] = {
};
ATTRIBUTE_GROUPS(acpi_battery);
-/*
- * The Battery Hooking API
- *
- * This API is used inside other drivers that need to expose
- * platform-specific behaviour within the generic driver in a
- * generic way.
- *
- */
-
-static LIST_HEAD(acpi_battery_list);
-static LIST_HEAD(battery_hook_list);
-static DEFINE_MUTEX(hook_mutex);
-
-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
-{
- struct acpi_battery *battery;
-
- /*
- * In order to remove a hook, we first need to
- * de-register all the batteries that are registered.
- */
- list_for_each_entry(battery, &acpi_battery_list, list) {
- if (!hook->remove_battery(battery->bat, hook))
- power_supply_changed(battery->bat);
- }
- list_del_init(&hook->list);
-
- pr_info("hook unregistered: %s\n", hook->name);
-}
-
-void battery_hook_unregister(struct acpi_battery_hook *hook)
-{
- mutex_lock(&hook_mutex);
- /*
- * Ignore already unregistered battery hooks. This might happen
- * if a battery hook was previously unloaded due to an error when
- * adding a new battery.
- */
- if (!list_empty(&hook->list))
- battery_hook_unregister_unlocked(hook);
-
- mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(battery_hook_unregister);
-
-void battery_hook_register(struct acpi_battery_hook *hook)
-{
- struct acpi_battery *battery;
-
- mutex_lock(&hook_mutex);
- list_add(&hook->list, &battery_hook_list);
- /*
- * Now that the driver is registered, we need
- * to notify the hook that a battery is available
- * for each battery, so that the driver may add
- * its attributes.
- */
- list_for_each_entry(battery, &acpi_battery_list, list) {
- if (hook->add_battery(battery->bat, hook)) {
- /*
- * If a add-battery returns non-zero,
- * the registration of the hook has failed,
- * and we will not add it to the list of loaded
- * hooks.
- */
- pr_err("hook failed to load: %s", hook->name);
- battery_hook_unregister_unlocked(hook);
- goto end;
- }
-
- power_supply_changed(battery->bat);
- }
- pr_info("new hook: %s\n", hook->name);
-end:
- mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(battery_hook_register);
-
-static void devm_battery_hook_unregister(void *data)
-{
- struct acpi_battery_hook *hook = data;
-
- battery_hook_unregister(hook);
-}
-
-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
-{
- battery_hook_register(hook);
-
- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
-}
-EXPORT_SYMBOL_GPL(devm_battery_hook_register);
-
-/*
- * This function gets called right after the battery sysfs
- * attributes have been added, so that the drivers that
- * define custom sysfs attributes can add their own.
- */
-static void battery_hook_add_battery(struct acpi_battery *battery)
-{
- struct acpi_battery_hook *hook_node, *tmp;
-
- mutex_lock(&hook_mutex);
- INIT_LIST_HEAD(&battery->list);
- list_add(&battery->list, &acpi_battery_list);
- /*
- * Since we added a new battery to the list, we need to
- * iterate over the hooks and call add_battery for each
- * hook that was registered. This usually happens
- * when a battery gets hotplugged or initialized
- * during the battery module initialization.
- */
- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
- if (hook_node->add_battery(battery->bat, hook_node)) {
- /*
- * The notification of the hook has failed, to
- * prevent further errors we will unload the hook.
- */
- pr_err("error in hook, unloading: %s",
- hook_node->name);
- battery_hook_unregister_unlocked(hook_node);
- }
- }
- mutex_unlock(&hook_mutex);
-}
-
-static void battery_hook_remove_battery(struct acpi_battery *battery)
-{
- struct acpi_battery_hook *hook;
-
- mutex_lock(&hook_mutex);
- /*
- * Before removing the hook, we need to remove all
- * custom attributes from the battery.
- */
- list_for_each_entry(hook, &battery_hook_list, list) {
- hook->remove_battery(battery->bat, hook);
- }
- /* Then, just remove the battery from the list */
- list_del(&battery->list);
- mutex_unlock(&hook_mutex);
-}
-
-static void __exit battery_hook_exit(void)
-{
- struct acpi_battery_hook *hook;
- struct acpi_battery_hook *ptr;
- /*
- * At this point, the acpi_bus_unregister_driver()
- * has called remove for all batteries. We just
- * need to remove the hooks.
- */
- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) {
- battery_hook_unregister(hook);
- }
- mutex_destroy(&hook_mutex);
-}
-
static int sysfs_add_battery(struct acpi_battery *battery)
{
struct power_supply_config psy_cfg = {
@@ -957,7 +799,7 @@ static int sysfs_add_battery(struct acpi_battery *battery)
battery->bat = NULL;
return result;
}
- battery_hook_add_battery(battery);
+ battery_hook_add_battery(&battery->hook_dev, battery->bat);
return 0;
}
@@ -966,7 +808,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
if (!battery->bat)
return;
- battery_hook_remove_battery(battery);
+ battery_hook_remove_battery(&battery->hook_dev);
power_supply_unregister(battery->bat);
battery->bat = NULL;
}
@@ -1476,7 +1318,6 @@ static int __init acpi_battery_init(void)
static void __exit acpi_battery_exit(void)
{
platform_driver_unregister(&acpi_battery_driver);
- battery_hook_exit();
}
module_init(acpi_battery_init);
diff --git a/drivers/acpi/battery_hook.c b/drivers/acpi/battery_hook.c
new file mode 100644
index 00000000000..308d10c940e
--- /dev/null
+++ b/drivers/acpi/battery_hook.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Battery Hooking API
+ *
+ * Provides a common registration and notification framework for drivers
+ * that attach platform-specific properties or extensions to ACPI-registered
+ * batteries (both ACPI Control Method batteries and Smart Battery Systems).
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
+
+static LIST_HEAD(acpi_battery_list);
+static LIST_HEAD(battery_hook_list);
+static DEFINE_MUTEX(hook_mutex);
+
+static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
+{
+ struct acpi_battery_hook_device *hook_dev;
+
+ list_for_each_entry(hook_dev, &acpi_battery_list, list) {
+ if (!hook->remove_battery(hook_dev->battery, hook))
+ power_supply_changed(hook_dev->battery);
+ }
+ list_del_init(&hook->list);
+
+ pr_info("hook unregistered: %s\n", hook->name);
+}
+
+void battery_hook_unregister(struct acpi_battery_hook *hook)
+{
+ mutex_lock(&hook_mutex);
+ if (!list_empty(&hook->list))
+ battery_hook_unregister_unlocked(hook);
+ mutex_unlock(&hook_mutex);
+}
+EXPORT_SYMBOL_GPL(battery_hook_unregister);
+
+void battery_hook_register(struct acpi_battery_hook *hook)
+{
+ struct acpi_battery_hook_device *hook_dev;
+
+ mutex_lock(&hook_mutex);
+ list_add(&hook->list, &battery_hook_list);
+
+ list_for_each_entry(hook_dev, &acpi_battery_list, list) {
+ if (hook->add_battery(hook_dev->battery, hook)) {
+ pr_err("hook failed to load: %s\n", hook->name);
+ battery_hook_unregister_unlocked(hook);
+ goto end;
+ }
+
+ power_supply_changed(hook_dev->battery);
+ }
+ pr_info("new hook: %s\n", hook->name);
+end:
+ mutex_unlock(&hook_mutex);
+}
+EXPORT_SYMBOL_GPL(battery_hook_register);
+
+static void devm_battery_hook_unregister(void *data)
+{
+ struct acpi_battery_hook *hook = data;
+
+ battery_hook_unregister(hook);
+}
+
+int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
+{
+ battery_hook_register(hook);
+
+ return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
+}
+EXPORT_SYMBOL_GPL(devm_battery_hook_register);
+
+void battery_hook_add_battery(struct acpi_battery_hook_device *hook_dev,
+ struct power_supply *battery)
+{
+ struct acpi_battery_hook *hook_node, *tmp;
+
+ hook_dev->battery = battery;
+ mutex_lock(&hook_mutex);
+ INIT_LIST_HEAD(&hook_dev->list);
+ list_add(&hook_dev->list, &acpi_battery_list);
+
+ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
+ if (hook_node->add_battery(hook_dev->battery, hook_node)) {
+ pr_err("error in hook, unloading: %s\n", hook_node->name);
+ battery_hook_unregister_unlocked(hook_node);
+ }
+ }
+ mutex_unlock(&hook_mutex);
+}
+EXPORT_SYMBOL_GPL(battery_hook_add_battery);
+
+void battery_hook_remove_battery(struct acpi_battery_hook_device *hook_dev)
+{
+ struct acpi_battery_hook *hook;
+
+ mutex_lock(&hook_mutex);
+ list_for_each_entry(hook, &battery_hook_list, list)
+ hook->remove_battery(hook_dev->battery, hook);
+
+ list_del(&hook_dev->list);
+ hook_dev->battery = NULL;
+ mutex_unlock(&hook_mutex);
+}
+EXPORT_SYMBOL_GPL(battery_hook_remove_battery);
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
index 86b7c797585..a2f27c0d233 100644
--- a/drivers/acpi/sbs.c
+++ b/drivers/acpi/sbs.c
@@ -10,6 +10,7 @@
#define pr_fmt(fmt) "ACPI: " fmt
#include <linux/init.h>
+#include <linux/list.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -54,6 +55,7 @@ struct acpi_battery {
struct power_supply *bat;
struct power_supply_desc bat_desc;
struct acpi_sbs *sbs;
+ struct acpi_battery_hook_device hook_dev;
unsigned long update_time;
char name[8];
char manufacturer_name[ACPI_SBS_BLOCK_MAX];
@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id)
goto end;
}
+ battery_hook_add_battery(&battery->hook_dev, battery->bat);
+
end:
pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n",
ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device),
@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id)
{
struct acpi_battery *battery = &sbs->battery[id];
- if (battery->bat)
+ if (battery->bat) {
+ battery_hook_remove_battery(&battery->hook_dev);
power_supply_unregister(battery->bat);
+ }
}
static int acpi_charger_add(struct acpi_sbs *sbs)
diff --git a/include/acpi/battery.h b/include/acpi/battery.h
index c93f16dfb94..df5087c0529 100644
--- a/include/acpi/battery.h
+++ b/include/acpi/battery.h
@@ -3,6 +3,7 @@
#define __ACPI_BATTERY_H
#include <linux/device.h>
+#include <linux/list.h>
#include <linux/power_supply.h>
#define ACPI_BATTERY_CLASS "battery"
@@ -18,8 +19,17 @@ struct acpi_battery_hook {
struct list_head list;
};
+struct acpi_battery_hook_device {
+ struct power_supply *battery;
+ struct list_head list;
+};
+
void battery_hook_register(struct acpi_battery_hook *hook);
void battery_hook_unregister(struct acpi_battery_hook *hook);
int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook);
+void battery_hook_add_battery(struct acpi_battery_hook_device *hook_dev,
+ struct power_supply *battery);
+void battery_hook_remove_battery(struct acpi_battery_hook_device *hook_dev);
+
#endif
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] hwmon: (applesmc) add charge_control_end_threshold support
2026-09-18 17:50 [PATCH v2 0/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
2026-09-18 17:50 ` [PATCH v2 1/2] ACPI: battery: add unified battery hook mechanism for ACPI and SBS batteries Jordan Brough
@ 2026-09-18 17:50 ` Jordan Brough
1 sibling, 0 replies; 3+ messages in thread
From: Jordan Brough @ 2026-09-18 17:50 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Rafael J . Wysocki
Cc: Thomas Weißschuh, Henrik Rydberg, linux-hwmon, linux-acpi,
linux-kernel, Jordan Brough
Intel MacBooks with Apple SMC expose battery charge control threshold
registers (BCLM) and MagSafe LED thresholds (BFCL).
Add native charge_control_end_threshold support to the applesmc driver
using the power_supply_ext extension API and the ACPI battery hook
infrastructure.
Hardware testing on Intel MacBooks shows that setting BCLM to a given
threshold requires keeping BFCL set to a margin (5%) below BCLM to
prevent a race where charging halts just before the SMC's sub-percent
counter reaches BFCL, leaving the MagSafe LED stuck in amber ("charging").
Serialize SMC register writes and power_supply extension registration under
applesmc_bat_lock to prevent concurrent write desynchronization and ensure
clean rollback if extension registration fails.
Signed-off-by: Jordan Brough <jordan@brough.org>
---
drivers/hwmon/applesmc.c | 157 +++++++++++++++++++++++++++++++++++++--
1 file changed, 152 insertions(+), 5 deletions(-)
diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index d0baa10502f..2444bea8a0f 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -33,6 +33,8 @@
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/bits.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
#include <asm/barrier.h>
/* data port used by Apple SMC */
@@ -76,6 +78,26 @@
#define TEMP_SENSOR_TYPE "sp78"
+/*
+ * BCLM caps charging at a percentage; it is the only key that affects
+ * charging behavior. BFCL only controls when the charging LED switches
+ * from orange to green and has no effect on charging itself.
+ */
+#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */
+#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */
+
+/*
+ * Margin kept between BCLM and BFCL so the charging LED still reports
+ * "done" once BCLM halts charging below 100%. Left equal to BCLM, the
+ * two can race: charging can stop a moment before the SMC's internal
+ * (sub-percent) charge counter actually reaches BFCL, leaving the LED
+ * stuck on "charging" indefinitely since no further current ever flows
+ * to push it over the threshold. A margin of 1 was sufficient to avoid
+ * this on a MacBookAir6,2; 5 matches Apple's own firmware convention and
+ * gives headroom on hardware we haven't tested.
+ */
+#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 5
+
/* List of keys used to read/write fan speeds */
static const char *const fan_speed_fmt[] = {
"F%dAc", /* actual speed */
@@ -131,6 +153,7 @@ static struct applesmc_registers {
int num_light_sensors; /* number of light sensors */
bool has_accelerometer; /* has motion sensor */
bool has_key_backlight; /* has keyboard backlight */
+ bool has_battery_charge_limit; /* has BCLM battery charge limit */
bool init_complete; /* true when fully initialized */
struct applesmc_entry *cache; /* cached key entries */
const char **index; /* temperature key index */
@@ -633,17 +656,21 @@ static int applesmc_init_smcreg_try(void)
if (ret)
return ret;
ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight);
+ if (ret)
+ return ret;
+ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit);
if (ret)
return ret;
s->num_light_sensors = left_light_sensor + right_light_sensor;
s->init_complete = true;
- pr_info("key=%d fan=%d temp=%d index=%d acc=%d lux=%d kbd=%d\n",
- s->key_count, s->fan_count, s->temp_count, s->index_count,
- s->has_accelerometer,
- s->num_light_sensors,
- s->has_key_backlight);
+ pr_info("key=%d fan=%d temp=%d index=%d acc=%d lux=%d kbd=%d bat=%d\n",
+ s->key_count, s->fan_count, s->temp_count, s->index_count,
+ s->has_accelerometer,
+ s->num_light_sensors,
+ s->has_key_backlight,
+ s->has_battery_charge_limit);
return 0;
}
@@ -724,6 +751,119 @@ static struct platform_driver applesmc_driver = {
},
};
+static DEFINE_MUTEX(applesmc_bat_lock);
+
+static int applesmc_bat_get_property(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ u8 limit;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ scoped_guard(mutex, &applesmc_bat_lock) {
+ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1);
+ if (ret)
+ return ret;
+ val->intval = limit;
+ return 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int applesmc_bat_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)
+{
+ u8 limit, led_limit;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval < 20 || val->intval > 100)
+ return -EINVAL;
+
+ limit = val->intval;
+ led_limit = limit > APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN ?
+ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit;
+
+ scoped_guard(mutex, &applesmc_bat_lock) {
+ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1);
+ if (ret)
+ return ret;
+
+ return applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY, &led_limit, 1);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int applesmc_bat_property_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
+}
+
+static const enum power_supply_property applesmc_bat_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext applesmc_bat_ext = {
+ .name = "applesmc-charge-control",
+ .properties = applesmc_bat_props,
+ .num_properties = ARRAY_SIZE(applesmc_bat_props),
+ .get_property = applesmc_bat_get_property,
+ .set_property = applesmc_bat_set_property,
+ .property_is_writeable = applesmc_bat_property_is_writeable,
+};
+
+static struct power_supply *applesmc_hooked_battery;
+
+static int applesmc_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ int ret;
+
+ guard(mutex)(&applesmc_bat_lock);
+ if (applesmc_hooked_battery)
+ return 0;
+
+ ret = power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL);
+ if (ret)
+ return ret;
+
+ applesmc_hooked_battery = battery;
+ return 0;
+}
+
+static int applesmc_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ guard(mutex)(&applesmc_bat_lock);
+ if (applesmc_hooked_battery != battery)
+ return 0;
+
+ power_supply_unregister_extension(battery, &applesmc_bat_ext);
+ applesmc_hooked_battery = NULL;
+ return 0;
+}
+
+static struct acpi_battery_hook applesmc_battery_hook = {
+ .name = "Apple SMC Battery Charge Control",
+ .add_battery = applesmc_battery_add,
+ .remove_battery = applesmc_battery_remove,
+};
+
/*
* applesmc_calibrate - Set our "resting" values. Callers must
* hold applesmc_lock.
@@ -1563,6 +1703,10 @@ static int __init applesmc_init(void)
goto out_light_ledclass;
}
+ if (smcreg.has_battery_charge_limit &&
+ (IS_REACHABLE(CONFIG_ACPI_BATTERY) || IS_REACHABLE(CONFIG_ACPI_SBS)))
+ battery_hook_register(&applesmc_battery_hook);
+
return 0;
out_light_ledclass:
@@ -1589,6 +1733,9 @@ static int __init applesmc_init(void)
static void __exit applesmc_exit(void)
{
+ if (smcreg.has_battery_charge_limit &&
+ (IS_REACHABLE(CONFIG_ACPI_BATTERY) || IS_REACHABLE(CONFIG_ACPI_SBS)))
+ battery_hook_unregister(&applesmc_battery_hook);
hwmon_device_unregister(hwmon_dev);
applesmc_release_key_backlight();
applesmc_release_light_sensor();
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-18 17:51 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 17:50 [PATCH v2 0/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
2026-09-18 17:50 ` [PATCH v2 1/2] ACPI: battery: add unified battery hook mechanism for ACPI and SBS batteries Jordan Brough
2026-09-18 17:50 ` [PATCH v2 2/2] hwmon: (applesmc) add charge_control_end_threshold support Jordan Brough
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®