From 9029409d1a250da19f1086ab1113752411c5163d Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 10 Dec 2024 22:55:49 +0100 Subject: power: supply: core: introduce power_supply_for_each_psy() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All existing callers of power_supply_for_each_device() want to iterate over 'struct power_supply', not 'struct device'. The power_supply_for_each_device() forces each caller to duplicate the logic to go from one to the other. Introduce power_supply_for_each_psy() to simplify the callers. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241210-power-supply-dev_to_psy-v2-2-9d8c9d24cfe4@weissschuh.net Signed-off-by: Sebastian Reichel --- include/linux/power_supply.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index b98106e1a90f..11d54270eaa9 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -882,6 +882,7 @@ extern int power_supply_powers(struct power_supply *psy, struct device *dev); extern void *power_supply_get_drvdata(struct power_supply *psy); extern int power_supply_for_each_device(void *data, int (*fn)(struct device *dev, void *data)); +extern int power_supply_for_each_psy(void *data, int (*fn)(struct power_supply *psy, void *data)); static inline bool power_supply_is_amp_property(enum power_supply_property psp) { -- cgit v1.2.3 From bfc330323cf3ea6d5c9985179384c0b56f2d5372 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 10 Dec 2024 22:55:53 +0100 Subject: power: supply: core: remove power_supply_for_each_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are no users anymore. All potential future users are expected to use power_supply_for_each_psy(). Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241210-power-supply-dev_to_psy-v2-6-9d8c9d24cfe4@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 8 +------- include/linux/power_supply.h | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 9adea87c6912..0cdccfd585cb 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -115,12 +115,6 @@ static void power_supply_changed_work(struct work_struct *work) spin_unlock_irqrestore(&psy->changed_lock, flags); } -int power_supply_for_each_device(void *data, int (*fn)(struct device *dev, void *data)) -{ - return class_for_each_device(&power_supply_class, NULL, data, fn); -} -EXPORT_SYMBOL_GPL(power_supply_for_each_device); - struct psy_for_each_psy_cb_data { int (*fn)(struct power_supply *psy, void *data); void *data; @@ -141,7 +135,7 @@ int power_supply_for_each_psy(void *data, int (*fn)(struct power_supply *psy, vo .data = data, }; - return power_supply_for_each_device(&cb_data, psy_for_each_psy_cb); + return class_for_each_device(&power_supply_class, NULL, &cb_data, psy_for_each_psy_cb); } EXPORT_SYMBOL_GPL(power_supply_for_each_psy); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 11d54270eaa9..3d67f4a6a1c9 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -881,7 +881,6 @@ extern int power_supply_powers(struct power_supply *psy, struct device *dev); #define to_power_supply(device) container_of(device, struct power_supply, dev) extern void *power_supply_get_drvdata(struct power_supply *psy); -extern int power_supply_for_each_device(void *data, int (*fn)(struct device *dev, void *data)); extern int power_supply_for_each_psy(void *data, int (*fn)(struct power_supply *psy, void *data)); static inline bool power_supply_is_amp_property(enum power_supply_property psp) -- cgit v1.2.3 From f52204036326bd9c07db08bab6607f423c801716 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 10 Dec 2024 22:55:54 +0100 Subject: power: supply: core: introduce dev_to_psy() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The psy core and drivers currently use dev_get_drvdata() to go from a 'struct device' to its 'struct power_supply'. This is not typesafe and or documented. Introduce a new helper to make this pattern explicit. Instead of using dev_get_drvdata(), use container_of_const() which also preserves the constness. Furthermore 'dev' does need to be dereferenced anymore and at some point the drvdata could be reused for something else. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241210-power-supply-dev_to_psy-v2-7-9d8c9d24cfe4@weissschuh.net Signed-off-by: Sebastian Reichel --- include/linux/power_supply.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 3d67f4a6a1c9..17fc383785bf 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -318,6 +318,8 @@ struct power_supply { #endif }; +#define dev_to_psy(__dev) container_of_const(__dev, struct power_supply, dev) + /* * This is recommended structure to specify static power supply parameters. * Generic one, parametrizable for different power supplies. Power supply -- cgit v1.2.3 From d24bf99214b199c25f9c2cb04b3a4993d1c7ab60 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 11 Dec 2024 18:44:49 +0100 Subject: power: supply: core: Add new "charge_types" property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new "charge_types" property, this is identical to "charge_type" but reading returns a list of supported charge-types with the currently active type surrounded by square brackets, e.g.: Fast [Standard] "Long_Life" This has the advantage over the existing "charge_type" property that this allows userspace to find out which charge-types are supported for writable charge_type properties. Drivers which already support "charge_type" can easily add support for this by setting power_supply_desc.charge_types to a bitmask representing valid charge_type values. The existing "charge_type" get_property() and set_property() code paths can be re-used for "charge_types". Signed-off-by: Hans de Goede Reviewed-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241211174451.355421-3-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 20 ++++++++++++++++ drivers/power/supply/power_supply_sysfs.c | 36 +++++++++++++++++++++++++++++ include/linux/power_supply.h | 23 +++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 45180b62d426..74050dfb5fc0 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -407,10 +407,30 @@ Description: Access: Read, Write + Reading this returns the current active value, e.g. 'Standard'. + Check charge_types to get the values supported by the battery. + Valid values: "Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom", "Long Life", "Bypass" +What: /sys/class/power_supply//charge_types +Date: December 2024 +Contact: linux-pm@vger.kernel.org +Description: + Identical to charge_type but reading returns a list of supported + charge-types with the currently active type surrounded by square + brackets, e.g.: "Fast [Standard] Long_Life". + + power_supply class devices may support both charge_type and + charge_types for backward compatibility. In this case both will + always have the same active value and the active value can be + changed by writing either property. + + Note charge-types which contain a space such as "Long Life" will + have the space replaced by a '_' resulting in e.g. "Long_Life". + When writing charge-types both variants are accepted. + What: /sys/class/power_supply//charge_term_current Date: July 2014 Contact: linux-pm@vger.kernel.org diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 9b604a1bcd17..e18f1ee53f21 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -182,6 +182,8 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = { POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD), POWER_SUPPLY_ATTR(CHARGE_CONTROL_END_THRESHOLD), POWER_SUPPLY_ENUM_ATTR(CHARGE_BEHAVIOUR), + /* Same enum value texts as "charge_type" without the 's' at the end */ + _POWER_SUPPLY_ENUM_ATTR(CHARGE_TYPES, POWER_SUPPLY_CHARGE_TYPE_TEXT), POWER_SUPPLY_ATTR(INPUT_CURRENT_LIMIT), POWER_SUPPLY_ATTR(INPUT_VOLTAGE_LIMIT), POWER_SUPPLY_ATTR(INPUT_POWER_LIMIT), @@ -339,6 +341,12 @@ static ssize_t power_supply_format_property(struct device *dev, ret = power_supply_charge_behaviour_show(dev, psy->desc->charge_behaviours, value.intval, buf); break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + if (uevent) /* no possible values in uevents */ + goto default_format; + ret = power_supply_charge_types_show(dev, psy->desc->charge_types, + value.intval, buf); + break; case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = sysfs_emit(buf, "%s\n", value.strval); break; @@ -556,3 +564,31 @@ int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const return -EINVAL; } EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_parse); + +ssize_t power_supply_charge_types_show(struct device *dev, + unsigned int available_types, + enum power_supply_charge_type current_type, + char *buf) +{ + return power_supply_show_enum_with_available( + dev, POWER_SUPPLY_CHARGE_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_TYPE_TEXT), + available_types, current_type, buf); +} +EXPORT_SYMBOL_GPL(power_supply_charge_types_show); + +int power_supply_charge_types_parse(unsigned int available_types, const char *buf) +{ + int i = power_supply_match_string(POWER_SUPPLY_CHARGE_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_TYPE_TEXT), + buf); + + if (i < 0) + return i; + + if (available_types & BIT(i)) + return i; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(power_supply_charge_types_parse); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 17fc383785bf..0d96657d1a2b 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -40,7 +40,7 @@ enum { }; /* What algorithm is the charger using? */ -enum { +enum power_supply_charge_type { POWER_SUPPLY_CHARGE_TYPE_UNKNOWN = 0, POWER_SUPPLY_CHARGE_TYPE_NONE, POWER_SUPPLY_CHARGE_TYPE_TRICKLE, /* slow speed */ @@ -99,6 +99,7 @@ enum power_supply_property { /* Properties of type `int' */ POWER_SUPPLY_PROP_STATUS = 0, POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_TYPES, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, @@ -245,6 +246,7 @@ struct power_supply_desc { const char *name; enum power_supply_type type; u8 charge_behaviours; + u32 charge_types; u32 usb_types; const enum power_supply_property *properties; size_t num_properties; @@ -946,6 +948,11 @@ ssize_t power_supply_charge_behaviour_show(struct device *dev, char *buf); int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const char *buf); +ssize_t power_supply_charge_types_show(struct device *dev, + unsigned int available_types, + enum power_supply_charge_type current_type, + char *buf); +int power_supply_charge_types_parse(unsigned int available_types, const char *buf); #else static inline ssize_t power_supply_charge_behaviour_show(struct device *dev, @@ -961,6 +968,20 @@ static inline int power_supply_charge_behaviour_parse(unsigned int available_beh { return -EOPNOTSUPP; } + +static inline +ssize_t power_supply_charge_types_show(struct device *dev, + unsigned int available_types, + enum power_supply_charge_type current_type, + char *buf) +{ + return -EOPNOTSUPP; +} + +static inline int power_supply_charge_types_parse(unsigned int available_types, const char *buf) +{ + return -EOPNOTSUPP; +} #endif #endif /* __LINUX_POWER_SUPPLY_H__ */ -- cgit v1.2.3 From 6037802bbae892f3ad0c7b4c4faee39b967e32b0 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 11 Dec 2024 20:57:55 +0100 Subject: power: supply: core: implement extension API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Various drivers, mostly in platform/x86 extend the ACPI battery driver with additional sysfs attributes to implement more UAPIs than are exposed through ACPI by using various side-channels, like WMI, nonstandard ACPI or EC communication. While the created sysfs attributes look similar to the attributes provided by the powersupply core, there are various deficiencies: * They don't show up in uevent payload. * They can't be queried with the standard in-kernel APIs. * They don't work with triggers. * The extending driver has to reimplement all of the parsing, formatting and sysfs display logic. * Writing a extension driver is completely different from writing a normal power supply driver. This extension API avoids all of these issues. An extension is just a "struct power_supply_ext" with the same kind of callbacks as in a normal "struct power_supply_desc". The API is meant to be used via battery_hook_register(), the same way as the current extensions. Signed-off-by: Thomas Weißschuh Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20241211-power-supply-extensions-v6-1-9d9dc3f3d387@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply.h | 17 ++++ drivers/power/supply/power_supply_core.c | 162 ++++++++++++++++++++++++++++-- drivers/power/supply/power_supply_sysfs.c | 26 ++++- include/linux/power_supply.h | 33 ++++++ 4 files changed, 228 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h index 5dabbd895538..531785516d2a 100644 --- a/drivers/power/supply/power_supply.h +++ b/drivers/power/supply/power_supply.h @@ -9,6 +9,8 @@ * Modified: 2004, Oct Szabolcs Gyurko */ +#include + struct device; struct device_type; struct power_supply; @@ -17,6 +19,21 @@ extern int power_supply_property_is_writeable(struct power_supply *psy, enum power_supply_property psp); extern bool power_supply_has_property(struct power_supply *psy, enum power_supply_property psp); +extern bool power_supply_ext_has_property(const struct power_supply_ext *ext, + enum power_supply_property psp); + +struct power_supply_ext_registration { + struct list_head list_head; + const struct power_supply_ext *ext; + void *data; +}; + +/* Make sure that the macro is a single expression */ +#define power_supply_for_each_extension(pos, psy) \ + if ( ({ lockdep_assert_held(&(psy)->extensions_sem); 0; }) ) \ + ; \ + else \ + list_for_each_entry(pos, &(psy)->extensions, list_head) \ #ifdef CONFIG_SYSFS diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 1db27afb7017..29209c19ea67 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -80,6 +80,7 @@ static int __power_supply_changed_work(struct power_supply *pst, void *data) static void power_supply_changed_work(struct work_struct *work) { + int ret; unsigned long flags; struct power_supply *psy = container_of(work, struct power_supply, changed_work); @@ -87,6 +88,16 @@ static void power_supply_changed_work(struct work_struct *work) dev_dbg(&psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); + + if (unlikely(psy->update_groups)) { + psy->update_groups = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); + ret = sysfs_update_groups(&psy->dev.kobj, power_supply_dev_type.groups); + if (ret) + dev_warn(&psy->dev, "failed to update sysfs groups: %pe\n", ERR_PTR(ret)); + spin_lock_irqsave(&psy->changed_lock, flags); + } + /* * Check 'changed' here to avoid issues due to race between * power_supply_changed() and this routine. In worst case @@ -1208,15 +1219,34 @@ static bool psy_desc_has_property(const struct power_supply_desc *psy_desc, return found; } +bool power_supply_ext_has_property(const struct power_supply_ext *psy_ext, + enum power_supply_property psp) +{ + int i; + + for (i = 0; i < psy_ext->num_properties; i++) + if (psy_ext->properties[i] == psp) + return true; + + return false; +} + bool power_supply_has_property(struct power_supply *psy, enum power_supply_property psp) { + struct power_supply_ext_registration *reg; + if (psy_desc_has_property(psy->desc, psp)) return true; if (power_supply_battery_info_has_prop(psy->battery_info, psp)) return true; + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) + return true; + } + return false; } @@ -1224,12 +1254,21 @@ int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + struct power_supply_ext_registration *reg; + if (atomic_read(&psy->use_cnt) <= 0) { if (!psy->initialized) return -EAGAIN; return -ENODEV; } + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) + return reg->ext->get_property(psy, reg->ext, reg->data, psp, val); + } + } + if (psy_desc_has_property(psy->desc, psp)) return psy->desc->get_property(psy, psp, val); else if (power_supply_battery_info_has_prop(psy->battery_info, psp)) @@ -1243,7 +1282,24 @@ int power_supply_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { - if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property) + struct power_supply_ext_registration *reg; + + if (atomic_read(&psy->use_cnt) <= 0) + return -ENODEV; + + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) { + if (reg->ext->set_property) + return reg->ext->set_property(psy, reg->ext, reg->data, + psp, val); + else + return -ENODEV; + } + } + } + + if (!psy->desc->set_property) return -ENODEV; return psy->desc->set_property(psy, psp, val); @@ -1253,7 +1309,22 @@ EXPORT_SYMBOL_GPL(power_supply_set_property); int power_supply_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { - return psy->desc->property_is_writeable && psy->desc->property_is_writeable(psy, psp); + struct power_supply_ext_registration *reg; + + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, psp)) { + if (reg->ext->property_is_writeable) + return reg->ext->property_is_writeable(psy, reg->ext, + reg->data, psp); + else + return 0; + } + } + + if (!psy->desc->property_is_writeable) + return 0; + + return psy->desc->property_is_writeable(psy, psp); } void power_supply_external_power_changed(struct power_supply *psy) @@ -1272,6 +1343,76 @@ int power_supply_powers(struct power_supply *psy, struct device *dev) } EXPORT_SYMBOL_GPL(power_supply_powers); +static int power_supply_update_sysfs_and_hwmon(struct power_supply *psy) +{ + unsigned long flags; + + spin_lock_irqsave(&psy->changed_lock, flags); + psy->update_groups = true; + spin_unlock_irqrestore(&psy->changed_lock, flags); + + power_supply_changed(psy); + + power_supply_remove_hwmon_sysfs(psy); + return power_supply_add_hwmon_sysfs(psy); +} + +int power_supply_register_extension(struct power_supply *psy, const struct power_supply_ext *ext, + void *data) +{ + struct power_supply_ext_registration *reg; + size_t i; + int ret; + + if (!psy || !ext || !ext->properties || !ext->num_properties) + return -EINVAL; + + guard(rwsem_write)(&psy->extensions_sem); + + for (i = 0; i < ext->num_properties; i++) + if (power_supply_has_property(psy, ext->properties[i])) + return -EEXIST; + + reg = kmalloc(sizeof(*reg), GFP_KERNEL); + if (!reg) + return -ENOMEM; + + reg->ext = ext; + reg->data = data; + list_add(®->list_head, &psy->extensions); + + ret = power_supply_update_sysfs_and_hwmon(psy); + if (ret) + goto sysfs_hwmon_failed; + + return 0; + +sysfs_hwmon_failed: + list_del(®->list_head); + kfree(reg); + return ret; +} +EXPORT_SYMBOL_GPL(power_supply_register_extension); + +void power_supply_unregister_extension(struct power_supply *psy, const struct power_supply_ext *ext) +{ + struct power_supply_ext_registration *reg; + + guard(rwsem_write)(&psy->extensions_sem); + + power_supply_for_each_extension(reg, psy) { + if (reg->ext == ext) { + list_del(®->list_head); + kfree(reg); + power_supply_update_sysfs_and_hwmon(psy); + return; + } + } + + dev_warn(&psy->dev, "Trying to unregister invalid extension"); +} +EXPORT_SYMBOL_GPL(power_supply_unregister_extension); + static void power_supply_dev_release(struct device *dev) { struct power_supply *psy = to_power_supply(dev); @@ -1426,6 +1567,9 @@ __power_supply_register(struct device *parent, } spin_lock_init(&psy->changed_lock); + init_rwsem(&psy->extensions_sem); + INIT_LIST_HEAD(&psy->extensions); + rc = device_add(dev); if (rc) goto device_add_failed; @@ -1438,13 +1582,15 @@ __power_supply_register(struct device *parent, if (rc) goto register_thermal_failed; - rc = power_supply_create_triggers(psy); - if (rc) - goto create_triggers_failed; + scoped_guard(rwsem_read, &psy->extensions_sem) { + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; - rc = power_supply_add_hwmon_sysfs(psy); - if (rc) - goto add_hwmon_sysfs_failed; + rc = power_supply_add_hwmon_sysfs(psy); + if (rc) + goto add_hwmon_sysfs_failed; + } /* * Update use_cnt after any uevents (most notably from device_add()). diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index e18f1ee53f21..b8f73bc89b05 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -299,6 +299,27 @@ static ssize_t power_supply_show_enum_with_available( return count; } +static ssize_t power_supply_show_charge_behaviour(struct device *dev, + struct power_supply *psy, + union power_supply_propval *value, + char *buf) +{ + struct power_supply_ext_registration *reg; + + scoped_guard(rwsem_read, &psy->extensions_sem) { + power_supply_for_each_extension(reg, psy) { + if (power_supply_ext_has_property(reg->ext, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)) + return power_supply_charge_behaviour_show(dev, + reg->ext->charge_behaviours, + value->intval, buf); + } + } + + return power_supply_charge_behaviour_show(dev, psy->desc->charge_behaviours, + value->intval, buf); +} + static ssize_t power_supply_format_property(struct device *dev, bool uevent, struct device_attribute *attr, @@ -338,8 +359,7 @@ static ssize_t power_supply_format_property(struct device *dev, case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: if (uevent) /* no possible values in uevents */ goto default_format; - ret = power_supply_charge_behaviour_show(dev, psy->desc->charge_behaviours, - value.intval, buf); + ret = power_supply_show_charge_behaviour(dev, psy, &value, buf); break; case POWER_SUPPLY_PROP_CHARGE_TYPES: if (uevent) /* no possible values in uevents */ @@ -422,6 +442,8 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, if (attrno == POWER_SUPPLY_PROP_TYPE) return mode; + guard(rwsem_read)(&psy->extensions_sem); + if (power_supply_has_property(psy, attrno)) { if (power_supply_property_is_writeable(psy, attrno) > 0) mode |= S_IWUSR; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 0d96657d1a2b..a877518cd963 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -283,6 +285,27 @@ struct power_supply_desc { int use_for_apm; }; +struct power_supply_ext { + u8 charge_behaviours; + const enum power_supply_property *properties; + size_t num_properties; + + int (*get_property)(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val); + int (*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); + int (*property_is_writeable)(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp); +}; + struct power_supply { const struct power_supply_desc *desc; @@ -302,10 +325,13 @@ struct power_supply { struct delayed_work deferred_register_work; spinlock_t changed_lock; bool changed; + bool update_groups; bool initialized; bool removing; atomic_t use_cnt; struct power_supply_battery_info *battery_info; + struct rw_semaphore extensions_sem; /* protects "extensions" */ + struct list_head extensions; #ifdef CONFIG_THERMAL struct thermal_zone_device *tzd; struct thermal_cooling_device *tcd; @@ -882,6 +908,13 @@ devm_power_supply_register(struct device *parent, extern void power_supply_unregister(struct power_supply *psy); extern int power_supply_powers(struct power_supply *psy, struct device *dev); +extern int __must_check +power_supply_register_extension(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data); +extern void power_supply_unregister_extension(struct power_supply *psy, + const struct power_supply_ext *ext); + #define to_power_supply(device) container_of(device, struct power_supply, dev) extern void *power_supply_get_drvdata(struct power_supply *psy); -- cgit v1.2.3 From 288a2cabcf6bb35532e8b2708829bdc2b85bc690 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 11 Dec 2024 20:57:58 +0100 Subject: power: supply: core: add UAPI to discover currently used extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Userspace wants to now about the used power supply extensions, for example to handle a device extended by a certain extension differently or to discover information about the extending device. Add a sysfs directory to the power supply device. This directory contains links which are named after the used extension and point to the device implementing that extension. Signed-off-by: Thomas Weißschuh Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20241211-power-supply-extensions-v6-4-9d9dc3f3d387@weissschuh.net Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 9 +++++++++ drivers/power/supply/cros_charge-control.c | 5 ++++- drivers/power/supply/power_supply.h | 2 ++ drivers/power/supply/power_supply_core.c | 19 +++++++++++++++++-- drivers/power/supply/power_supply_sysfs.c | 10 ++++++++++ drivers/power/supply/test_power.c | 4 +++- include/linux/power_supply.h | 2 ++ 7 files changed, 47 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 74050dfb5fc0..cb53dde20a80 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -813,3 +813,12 @@ Description: Access: Read Valid values: 1-31 + +What: /sys/class/power_supply//extensions/ +Date: March 2025 +Contact: linux-pm@vger.kernel.org +Description: + Reports the extensions registered to the power supply. + Each entry is a link to the device which registered the extension. + + Access: Read diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index fb4af232721d..02d5bdbe2e8d 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -31,6 +31,7 @@ */ struct cros_chctl_priv { + struct device *dev; struct cros_ec_device *cros_ec; struct acpi_battery_hook battery_hook; struct power_supply *hooked_battery; @@ -202,6 +203,7 @@ static int cros_chctl_psy_prop_is_writeable(struct power_supply *psy, }; \ \ static const struct power_supply_ext _name = { \ + .name = "cros-charge-control", \ .properties = _name ## _props, \ .num_properties = ARRAY_SIZE(_name ## _props), \ .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS, \ @@ -233,7 +235,7 @@ static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_batt return 0; priv->hooked_battery = battery; - return power_supply_register_extension(battery, priv->psy_ext, priv); + return power_supply_register_extension(battery, priv->psy_ext, priv->dev, priv); } static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) @@ -299,6 +301,7 @@ static int cros_chctl_probe(struct platform_device *pdev) dev_dbg(dev, "Command version: %u\n", (unsigned int)priv->cmd_version); + priv->dev = dev; priv->cros_ec = cros_ec; if (priv->cmd_version == 1) diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h index 531785516d2a..9ed749cd0936 100644 --- a/drivers/power/supply/power_supply.h +++ b/drivers/power/supply/power_supply.h @@ -25,6 +25,7 @@ extern bool power_supply_ext_has_property(const struct power_supply_ext *ext, struct power_supply_ext_registration { struct list_head list_head; const struct power_supply_ext *ext; + struct device *dev; void *data; }; @@ -39,6 +40,7 @@ struct power_supply_ext_registration { extern void __init power_supply_init_attrs(void); extern int power_supply_uevent(const struct device *dev, struct kobj_uevent_env *env); +extern const struct attribute_group power_supply_extension_group; extern const struct attribute_group *power_supply_attr_groups[]; #else diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 29209c19ea67..76ebaef403ed 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1358,17 +1358,21 @@ static int power_supply_update_sysfs_and_hwmon(struct power_supply *psy) } int power_supply_register_extension(struct power_supply *psy, const struct power_supply_ext *ext, - void *data) + struct device *dev, void *data) { struct power_supply_ext_registration *reg; size_t i; int ret; - if (!psy || !ext || !ext->properties || !ext->num_properties) + if (!psy || !dev || !ext || !ext->name || !ext->properties || !ext->num_properties) return -EINVAL; guard(rwsem_write)(&psy->extensions_sem); + power_supply_for_each_extension(reg, psy) + if (strcmp(ext->name, reg->ext->name) == 0) + return -EEXIST; + for (i = 0; i < ext->num_properties; i++) if (power_supply_has_property(psy, ext->properties[i])) return -EEXIST; @@ -1378,9 +1382,15 @@ int power_supply_register_extension(struct power_supply *psy, const struct power return -ENOMEM; reg->ext = ext; + reg->dev = dev; reg->data = data; list_add(®->list_head, &psy->extensions); + ret = sysfs_add_link_to_group(&psy->dev.kobj, power_supply_extension_group.name, + &dev->kobj, ext->name); + if (ret) + goto sysfs_link_failed; + ret = power_supply_update_sysfs_and_hwmon(psy); if (ret) goto sysfs_hwmon_failed; @@ -1388,6 +1398,8 @@ int power_supply_register_extension(struct power_supply *psy, const struct power return 0; sysfs_hwmon_failed: + sysfs_remove_link_from_group(&psy->dev.kobj, power_supply_extension_group.name, ext->name); +sysfs_link_failed: list_del(®->list_head); kfree(reg); return ret; @@ -1403,6 +1415,9 @@ void power_supply_unregister_extension(struct power_supply *psy, const struct po power_supply_for_each_extension(reg, psy) { if (reg->ext == ext) { list_del(®->list_head); + sysfs_remove_link_from_group(&psy->dev.kobj, + power_supply_extension_group.name, + reg->ext->name); kfree(reg); power_supply_update_sysfs_and_hwmon(psy); return; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index b8f73bc89b05..261bf7bf6e22 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -458,8 +458,18 @@ static const struct attribute_group power_supply_attr_group = { .is_visible = power_supply_attr_is_visible, }; +static struct attribute *power_supply_extension_attrs[] = { + NULL +}; + +const struct attribute_group power_supply_extension_group = { + .name = "extensions", + .attrs = power_supply_extension_attrs, +}; + const struct attribute_group *power_supply_attr_groups[] = { &power_supply_attr_group, + &power_supply_extension_group, NULL }; diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c index 66f9ef52e0f3..2a975a110f48 100644 --- a/drivers/power/supply/test_power.c +++ b/drivers/power/supply/test_power.c @@ -293,6 +293,7 @@ static int test_power_battery_extproperty_is_writeable(struct power_supply *psy, } static const struct power_supply_ext test_power_battery_ext = { + .name = "test_power", .properties = test_power_battery_extprops, .num_properties = ARRAY_SIZE(test_power_battery_extprops), .get_property = test_power_battery_extget_property, @@ -307,7 +308,8 @@ static void test_power_configure_battery_extension(bool enable) psy = test_power_supplies[TEST_BATTERY]; if (enable) { - if (power_supply_register_extension(psy, &test_power_battery_ext, NULL)) { + if (power_supply_register_extension(psy, &test_power_battery_ext, &psy->dev, + NULL)) { pr_err("registering battery extension failed\n"); return; } diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index a877518cd963..c3ce9f2b17d4 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -286,6 +286,7 @@ struct power_supply_desc { }; struct power_supply_ext { + const char *const name; u8 charge_behaviours; const enum power_supply_property *properties; size_t num_properties; @@ -911,6 +912,7 @@ extern int power_supply_powers(struct power_supply *psy, struct device *dev); extern int __must_check power_supply_register_extension(struct power_supply *psy, const struct power_supply_ext *ext, + struct device *dev, void *data); extern void power_supply_unregister_extension(struct power_supply *psy, const struct power_supply_ext *ext); -- cgit v1.2.3 From 525f6a2c63e0958c25080e108a0cb7f8a3a23719 Mon Sep 17 00:00:00 2001 From: "Sicelo A. Mhlongo" Date: Mon, 25 Nov 2024 17:12:58 +0200 Subject: bq27xxx: add voltage min design for bq27000 and bq27200 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bq27x00 gauges have an EEPROM register which contains the value of the voltage that should be considered to be zero battery capacity. Expose this to userspace using the VOLTAGE_MIN_DESIGN property. Tested on Nokia N900 with bq27200. Signed-off-by: Sicelo A. Mhlongo Acked-by: Pali Rohár Link: https://lore.kernel.org/r/20241125151321.45440-1-absicsz@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 39 +++++++++++++++++++++++++++++++++- include/linux/power/bq27xxx_battery.h | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 40c5ac7a1118..90a5bccfc6b9 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -123,6 +123,7 @@ enum bq27xxx_reg_index { BQ27XXX_DM_BLOCK, /* Data Block */ BQ27XXX_DM_DATA, /* Block Data */ BQ27XXX_DM_CKSUM, /* Block Data Checksum */ + BQ27XXX_REG_SEDVF, /* End-of-discharge Voltage */ BQ27XXX_REG_MAX, /* sentinel */ }; @@ -159,6 +160,7 @@ static u8 [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, + [BQ27XXX_REG_SEDVF] = 0x77, }, bq27010_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -184,6 +186,7 @@ static u8 [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, + [BQ27XXX_REG_SEDVF] = 0x77, }, bq2750x_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -579,6 +582,7 @@ static enum power_supply_property bq27000_props[] = { POWER_SUPPLY_PROP_POWER_AVG, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, }; static enum power_supply_property bq27010_props[] = { @@ -599,6 +603,7 @@ static enum power_supply_property bq27010_props[] = { POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, }; #define bq2750x_props bq27510g3_props @@ -2039,6 +2044,36 @@ static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, return 0; } +/* + * Return the design minimum battery Voltage in microvolts + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_dmin_volt(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int volt; + + /* We only have to read design minimum voltage once */ + if (di->voltage_min_design > 0) { + val->intval = di->voltage_min_design; + return 0; + } + + volt = bq27xxx_read(di, BQ27XXX_REG_SEDVF, true); + if (volt < 0) { + dev_err(di->dev, "error reading design min voltage\n"); + return volt; + } + + /* SEDVF = Design EDVF / 8 - 256 */ + val->intval = volt * 8000 + 2048000; + + /* Save for later reads */ + di->voltage_min_design = val->intval; + + return 0; +} + static int bq27xxx_simple_value(int value, union power_supply_propval *val) { @@ -2119,8 +2154,10 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, * power_supply_battery_info visible in sysfs. */ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: return -EINVAL; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = bq27xxx_battery_read_dmin_volt(di, val); + break; case POWER_SUPPLY_PROP_CYCLE_COUNT: ret = bq27xxx_battery_read_cyct(di, val); break; diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index 5180dc9f1706..6b190639b08e 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -61,6 +61,7 @@ struct bq27xxx_device_info { struct bq27xxx_access_methods bus; struct bq27xxx_reg_cache cache; int charge_design_full; + int voltage_min_design; bool removed; unsigned long last_update; union power_supply_propval last_status; -- cgit v1.2.3 From df998c22321dde3f70cd3cf8c183dfd6bf64c759 Mon Sep 17 00:00:00 2001 From: Dzmitry Sankouski Date: Wed, 8 Jan 2025 17:13:45 +0300 Subject: power: supply: add undervoltage health status property Add POWER_SUPPLY_HEALTH_UNDERVOLTAGE status for power supply to report under voltage lockout failures. Signed-off-by: Dzmitry Sankouski Link: https://lore.kernel.org/r/20250108-starqltechn_integration_upstream-v14-1-f6e84ec20d96@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 2 +- drivers/power/supply/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index cb53dde20a80..2a5c1a09a28f 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -453,7 +453,7 @@ Description: Valid values: "Unknown", "Good", "Overheat", "Dead", - "Over voltage", "Unspecified failure", "Cold", + "Over voltage", "Under voltage", "Unspecified failure", "Cold", "Watchdog timer expire", "Safety timer expire", "Over current", "Calibration required", "Warm", "Cool", "Hot", "No battery" diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 9ce5eda24093..edb058c19c9c 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -99,6 +99,7 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = { [POWER_SUPPLY_HEALTH_OVERHEAT] = "Overheat", [POWER_SUPPLY_HEALTH_DEAD] = "Dead", [POWER_SUPPLY_HEALTH_OVERVOLTAGE] = "Over voltage", + [POWER_SUPPLY_HEALTH_UNDERVOLTAGE] = "Under voltage", [POWER_SUPPLY_HEALTH_UNSPEC_FAILURE] = "Unspecified failure", [POWER_SUPPLY_HEALTH_COLD] = "Cold", [POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE] = "Watchdog timer expire", diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index c3ce9f2b17d4..6ed53b292162 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -60,6 +60,7 @@ enum { POWER_SUPPLY_HEALTH_OVERHEAT, POWER_SUPPLY_HEALTH_DEAD, POWER_SUPPLY_HEALTH_OVERVOLTAGE, + POWER_SUPPLY_HEALTH_UNDERVOLTAGE, POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, POWER_SUPPLY_HEALTH_COLD, POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE, -- cgit v1.2.3