From bcfe7d6ba20742bc166b293cc1a3986a0f4aaeb9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Dec 2024 20:57:57 +0100 Subject: [PATCH 01/16] power: supply: cros_charge-control: implement a power supply extension MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Power supply extensions provide an easier mechanism to implement additional properties for existing power supplies. Use that instead of reimplementing the sysfs attributes manually. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241211-power-supply-extensions-v6-3-9d9dc3f3d387@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_charge-control.c | 197 +++++++++------------ 1 file changed, 85 insertions(+), 112 deletions(-) diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index 9b0a7500296b..fb4af232721d 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -20,13 +20,6 @@ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) -enum CROS_CHCTL_ATTR { - CROS_CHCTL_ATTR_START_THRESHOLD, - CROS_CHCTL_ATTR_END_THRESHOLD, - CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR, - _CROS_CHCTL_ATTR_COUNT -}; - /* * Semantics of data *returned* from the EC API and Linux sysfs differ * slightly, also the v1 API can not return any data. @@ -43,13 +36,7 @@ struct cros_chctl_priv { struct power_supply *hooked_battery; u8 cmd_version; - /* The callbacks need to access this priv structure. - * As neither the struct device nor power_supply are under the drivers - * control, embed the attributes within priv to use with container_of(). - */ - struct device_attribute device_attrs[_CROS_CHCTL_ATTR_COUNT]; - struct attribute *attributes[_CROS_CHCTL_ATTR_COUNT]; - struct attribute_group group; + const struct power_supply_ext *psy_ext; struct mutex lock; /* protects fields below and cros_ec */ enum power_supply_charge_behaviour current_behaviour; @@ -119,26 +106,39 @@ static int cros_chctl_configure_ec(struct cros_chctl_priv *priv) return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req); } -static struct cros_chctl_priv *cros_chctl_attr_to_priv(struct attribute *attr, - enum CROS_CHCTL_ATTR idx) +static int cros_chctl_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) { - struct device_attribute *dev_attr = container_of(attr, struct device_attribute, attr); + struct cros_chctl_priv *priv = data; - return container_of(dev_attr, struct cros_chctl_priv, device_attrs[idx]); + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + val->intval = priv->current_start_threshold; + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + val->intval = priv->current_end_threshold; + return 0; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + val->intval = priv->current_behaviour; + return 0; + default: + return -EINVAL; + } } -static ssize_t cros_chctl_store_threshold(struct device *dev, struct cros_chctl_priv *priv, - int is_end_threshold, const char *buf, size_t count) +static int cros_chctl_psy_ext_set_threshold(struct cros_chctl_priv *priv, + enum power_supply_property psp, + int val) { - int ret, val; + int ret; - ret = kstrtoint(buf, 10, &val); - if (ret < 0) - return ret; if (val < 0 || val > 100) return -EINVAL; - if (is_end_threshold) { + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) { /* Start threshold is not exposed, use fixed value */ if (priv->cmd_version == 2) priv->current_start_threshold = val == 100 ? 0 : val; @@ -158,93 +158,72 @@ static ssize_t cros_chctl_store_threshold(struct device *dev, struct cros_chctl_ return ret; } - return count; -} - -static ssize_t charge_control_start_threshold_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_START_THRESHOLD); - - guard(mutex)(&priv->lock); - return sysfs_emit(buf, "%u\n", (unsigned int)priv->current_start_threshold); + return 0; } -static ssize_t charge_control_start_threshold_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_START_THRESHOLD); - - guard(mutex)(&priv->lock); - return cros_chctl_store_threshold(dev, priv, 0, buf, count); -} -static ssize_t charge_control_end_threshold_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int cros_chctl_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) { - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_END_THRESHOLD); + struct cros_chctl_priv *priv = data; + int ret; guard(mutex)(&priv->lock); - return sysfs_emit(buf, "%u\n", (unsigned int)priv->current_end_threshold); -} - -static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_END_THRESHOLD); - guard(mutex)(&priv->lock); - return cros_chctl_store_threshold(dev, priv, 1, buf, count); + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return cros_chctl_psy_ext_set_threshold(priv, psp, val->intval); + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + priv->current_behaviour = val->intval; + ret = cros_chctl_configure_ec(priv); + if (ret < 0) + return ret; + return 0; + default: + return -EINVAL; + } } -static ssize_t charge_behaviour_show(struct device *dev, struct device_attribute *attr, char *buf) +static int cros_chctl_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) { - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR); - - guard(mutex)(&priv->lock); - return power_supply_charge_behaviour_show(dev, EC_CHARGE_CONTROL_BEHAVIOURS, - priv->current_behaviour, buf); + return true; } -static ssize_t charge_behaviour_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr, - CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR); - int ret; - - ret = power_supply_charge_behaviour_parse(EC_CHARGE_CONTROL_BEHAVIOURS, buf); - if (ret < 0) - return ret; - - guard(mutex)(&priv->lock); - priv->current_behaviour = ret; +#define DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(_name, ...) \ + static const enum power_supply_property _name ## _props[] = { \ + __VA_ARGS__, \ + }; \ + \ + static const struct power_supply_ext _name = { \ + .properties = _name ## _props, \ + .num_properties = ARRAY_SIZE(_name ## _props), \ + .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS, \ + .get_property = cros_chctl_psy_ext_get_prop, \ + .set_property = cros_chctl_psy_ext_set_prop, \ + .property_is_writeable = cros_chctl_psy_prop_is_writeable, \ + } - ret = cros_chctl_configure_ec(priv); - if (ret < 0) - return ret; +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v1, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR +); - return count; -} +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v2, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD +); -static umode_t cros_chtl_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) -{ - struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(attr, n); - - if (n == CROS_CHCTL_ATTR_START_THRESHOLD && priv->cmd_version < 3) - return 0; - else if (n == CROS_CHCTL_ATTR_END_THRESHOLD && priv->cmd_version < 2) - return 0; - - return attr->mode; -} +DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v3, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD +); static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) { @@ -254,7 +233,7 @@ static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_batt return 0; priv->hooked_battery = battery; - return device_add_group(&battery->dev, &priv->group); + return power_supply_register_extension(battery, priv->psy_ext, priv); } static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) @@ -262,7 +241,7 @@ static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_b struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook); if (priv->hooked_battery == battery) { - device_remove_group(&battery->dev, &priv->group); + power_supply_unregister_extension(battery, priv->psy_ext); priv->hooked_battery = NULL; } @@ -288,7 +267,6 @@ static int cros_chctl_probe(struct platform_device *pdev) struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); struct cros_ec_device *cros_ec = ec_dev->ec_dev; struct cros_chctl_priv *priv; - size_t i; int ret; ret = cros_chctl_fwk_charge_control_versions(cros_ec); @@ -322,18 +300,13 @@ static int cros_chctl_probe(struct platform_device *pdev) dev_dbg(dev, "Command version: %u\n", (unsigned int)priv->cmd_version); priv->cros_ec = cros_ec; - priv->device_attrs[CROS_CHCTL_ATTR_START_THRESHOLD] = - (struct device_attribute)__ATTR_RW(charge_control_start_threshold); - priv->device_attrs[CROS_CHCTL_ATTR_END_THRESHOLD] = - (struct device_attribute)__ATTR_RW(charge_control_end_threshold); - priv->device_attrs[CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR] = - (struct device_attribute)__ATTR_RW(charge_behaviour); - for (i = 0; i < _CROS_CHCTL_ATTR_COUNT; i++) { - sysfs_attr_init(&priv->device_attrs[i].attr); - priv->attributes[i] = &priv->device_attrs[i].attr; - } - priv->group.is_visible = cros_chtl_attr_is_visible; - priv->group.attrs = priv->attributes; + + if (priv->cmd_version == 1) + priv->psy_ext = &cros_chctl_psy_ext_v1; + else if (priv->cmd_version == 2) + priv->psy_ext = &cros_chctl_psy_ext_v2; + else + priv->psy_ext = &cros_chctl_psy_ext_v3; priv->battery_hook.name = dev_name(dev); priv->battery_hook.add_battery = cros_chctl_add_battery; -- 2.51.0 From 288a2cabcf6bb35532e8b2708829bdc2b85bc690 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 11 Dec 2024 20:57:58 +0100 Subject: [PATCH 02/16] power: supply: core: add UAPI to discover currently used extensions MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 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(-) 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); -- 2.51.0 From da4ac0b3c22f48f6387617d13bb986f633d3c01a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 16 Nov 2024 21:36:46 +0100 Subject: [PATCH 03/16] dt-bindings: power: supply: bq24190: Add BQ24297 compatible The BQ24297 is identical to the BQ24296 except that it uses USB D+ / D- data-lines for charger-type (max. input-current) detection instead of a PSEL input pin. This is the same difference as between the already supported BQ24190 (D+ / D-) and the BQ24192 (PSEL). Signed-off-by: Hans de Goede Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20241116203648.169100-1-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/bq24190.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/supply/bq24190.yaml b/Documentation/devicetree/bindings/power/supply/bq24190.yaml index 131b7e57d22f..07adf88997b4 100644 --- a/Documentation/devicetree/bindings/power/supply/bq24190.yaml +++ b/Documentation/devicetree/bindings/power/supply/bq24190.yaml @@ -21,6 +21,7 @@ properties: - ti,bq24192i - ti,bq24196 - ti,bq24296 + - ti,bq24297 reg: maxItems: 1 -- 2.51.0 From 3c7c176b10ccff15fee0e8d00ebc6804f87fb807 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 16 Nov 2024 21:36:48 +0100 Subject: [PATCH 04/16] power: supply: bq24190: Add BQ24297 support The BQ24297 is identical to the BQ24296 except that it uses USB D+ / D- data-lines for charger-type (max. input-current) detection instead of a PSEL input pin. This is the same difference as between the already supported BQ24190 (D+ / D-) and the BQ24192 (PSEL). Note just like with the BQ24190 vs BQ24192 there is no difference how the charger-IC works at the register-level. The only difference is in the external hardware interface. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20241116203648.169100-3-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 1e49de324181..0b52faa9eb4c 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -152,6 +152,7 @@ #define BQ24296_REG_VPRS_PN_MASK (BIT(7) | BIT(6) | BIT(5)) #define BQ24296_REG_VPRS_PN_SHIFT 5 #define BQ24296_REG_VPRS_PN_24296 0x1 +#define BQ24296_REG_VPRS_PN_24297 0x3 #define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2) #define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2 #define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0)) @@ -208,6 +209,7 @@ enum bq24190_chip { BQ24192i, BQ24196, BQ24296, + BQ24297, }; /* @@ -1898,6 +1900,7 @@ static int bq24296_check_chip(struct bq24190_dev_info *bdi) switch (v) { case BQ24296_REG_VPRS_PN_24296: + case BQ24296_REG_VPRS_PN_24297: break; default: dev_err(bdi->dev, "Error unknown model: 0x%02x\n", v); @@ -2027,6 +2030,17 @@ static const struct bq24190_chip_info bq24190_chip_info_tbl[] = { .ichg_array_size = BQ24296_CCC_ICHG_VALUES_LEN, #ifdef CONFIG_REGULATOR .vbus_desc = &bq24296_vbus_desc, +#endif + .check_chip = bq24296_check_chip, + .set_chg_config = bq24296_battery_set_chg_config, + .ntc_fault_mask = BQ24296_REG_F_NTC_FAULT_MASK, + .get_ntc_status = bq24296_charger_get_ntc_status, + .set_otg_vbus = bq24296_set_otg_vbus, + }, + [BQ24297] = { + .ichg_array_size = BQ24296_CCC_ICHG_VALUES_LEN, +#ifdef CONFIG_REGULATOR + .vbus_desc = &bq24296_vbus_desc, #endif .check_chip = bq24296_check_chip, .set_chg_config = bq24296_battery_set_chg_config, @@ -2290,6 +2304,7 @@ static const struct i2c_device_id bq24190_i2c_ids[] = { { "bq24192i", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192i] }, { "bq24196", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24196] }, { "bq24296", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24296] }, + { "bq24297", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24297] }, { }, }; MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids); @@ -2300,6 +2315,7 @@ static const struct of_device_id bq24190_of_match[] = { { .compatible = "ti,bq24192i", .data = &bq24190_chip_info_tbl[BQ24192i] }, { .compatible = "ti,bq24196", .data = &bq24190_chip_info_tbl[BQ24196] }, { .compatible = "ti,bq24296", .data = &bq24190_chip_info_tbl[BQ24296] }, + { .compatible = "ti,bq24297", .data = &bq24190_chip_info_tbl[BQ24297] }, { }, }; MODULE_DEVICE_TABLE(of, bq24190_of_match); -- 2.51.0 From 156fb706f915ceb436200030e3730305bbf12f6c Mon Sep 17 00:00:00 2001 From: Nobuhiro Iwamatsu Date: Sun, 17 Nov 2024 14:34:43 +0900 Subject: [PATCH 05/16] power: reset: as3722-poweroff: Remove unnecessary return in as3722_poweroff_probe The return is executed on the devm_register_sys_off_handler() line in as3722_poweroff_probe, so the last return line is unnecessary. Remove it. Fixes: 348fde771ce7 ("power: reset: as3722-poweroff: Use devm_register_sys_off_handler(POWER_OFF)") Signed-off-by: Nobuhiro Iwamatsu Acked-by: Andrew Davis Link: https://lore.kernel.org/r/20241117053443.1148902-1-iwamatsu@nigauri.org Signed-off-by: Sebastian Reichel --- drivers/power/reset/as3722-poweroff.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c index bb26fa6fa67c..8075382cbc36 100644 --- a/drivers/power/reset/as3722-poweroff.c +++ b/drivers/power/reset/as3722-poweroff.c @@ -57,8 +57,6 @@ static int as3722_poweroff_probe(struct platform_device *pdev) SYS_OFF_PRIO_DEFAULT, as3722_pm_power_off, as3722_poweroff); - - return 0; } static struct platform_driver as3722_poweroff_driver = { -- 2.51.0 From 525f6a2c63e0958c25080e108a0cb7f8a3a23719 Mon Sep 17 00:00:00 2001 From: "Sicelo A. Mhlongo" Date: Mon, 25 Nov 2024 17:12:58 +0200 Subject: [PATCH 06/16] bq27xxx: add voltage min design for bq27000 and bq27200 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 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(-) 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; -- 2.51.0 From 5972da73f75af6002b72e5cd61002855b6b4eda3 Mon Sep 17 00:00:00 2001 From: "Sicelo A. Mhlongo" Date: Thu, 28 Nov 2024 10:57:28 +0200 Subject: [PATCH 07/16] power: supply: bq2415x_charger: report charging state changes to userspace Continuously track the charging status register in order to send uevents whenever the state changes. Generate an uevent also when the chip's OTG line is toggled, in bq2415x_notifier_call(). Signed-off-by: Sicelo A. Mhlongo Link: https://lore.kernel.org/r/20241128085759.11840-2-absicsz@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq2415x_charger.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c index 5e174242d2de..18a6c3dbc758 100644 --- a/drivers/power/supply/bq2415x_charger.c +++ b/drivers/power/supply/bq2415x_charger.c @@ -171,6 +171,7 @@ struct bq2415x_device { char *name; int autotimer; /* 1 - if driver automatically reset timer, 0 - not */ int automode; /* 1 - enabled, 0 - disabled; -1 - not supported */ + int charge_status; int id; }; @@ -835,6 +836,8 @@ static int bq2415x_notifier_call(struct notifier_block *nb, if (!bq2415x_update_reported_mode(bq, prop.intval)) return NOTIFY_OK; + power_supply_changed(bq->charger); + /* if automode is not enabled do not tell about reported_mode */ if (bq->automode < 1) return NOTIFY_OK; @@ -889,12 +892,19 @@ static void bq2415x_timer_work(struct work_struct *work) int ret; int error; int boost; + int charge; if (bq->automode > 0 && (bq->reported_mode != bq->mode)) { sysfs_notify(&bq->charger->dev.kobj, NULL, "reported_mode"); bq2415x_set_mode(bq, bq->reported_mode); } + charge = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS); + if (bq->charge_status != charge) { + power_supply_changed(bq->charger); + bq->charge_status = charge; + } + if (!bq->autotimer) return; -- 2.51.0 From 21096800c5ac634a9fbbac21505bfd798163b390 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 19 Dec 2024 15:28:18 +0100 Subject: [PATCH 08/16] power: supply: core: fix build of extension sysfs group if CONFIG_SYSFS=n MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Add and use wrapper functions for the sysfs interaction. Restore the compatibility of CONFIG_POWER_SUPPLY=y and CONFIG_SYSFS=n. Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/lkml/20241218195229.GA2796534@ax162/ Fixes: 288a2cabcf6b ("power: supply: core: add UAPI to discover currently used extensions") Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20241219-psy-extensions-sysfs-v1-1-868fc6cb46d6@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply.h | 12 +++++++++++- drivers/power/supply/power_supply_core.c | 13 +++++-------- drivers/power/supply/power_supply_sysfs.c | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h index 9ed749cd0936..8f6a2d44b996 100644 --- a/drivers/power/supply/power_supply.h +++ b/drivers/power/supply/power_supply.h @@ -40,14 +40,24 @@ 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[]; +extern int power_supply_sysfs_add_extension(struct power_supply *psy, + const struct power_supply_ext *ext, + struct device *dev); +extern void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext); #else static inline void power_supply_init_attrs(void) {} #define power_supply_attr_groups NULL #define power_supply_uevent NULL +static inline int power_supply_sysfs_add_extension(struct power_supply *psy, + const struct power_supply_ext *ext, + struct device *dev) +{ return 0; } +static inline void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext) {} #endif /* CONFIG_SYSFS */ diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 76ebaef403ed..eeeed1216e22 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1386,10 +1386,9 @@ int power_supply_register_extension(struct power_supply *psy, const struct power 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); + ret = power_supply_sysfs_add_extension(psy, ext, dev); if (ret) - goto sysfs_link_failed; + goto sysfs_add_failed; ret = power_supply_update_sysfs_and_hwmon(psy); if (ret) @@ -1398,8 +1397,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: + power_supply_sysfs_remove_extension(psy, ext); +sysfs_add_failed: list_del(®->list_head); kfree(reg); return ret; @@ -1415,9 +1414,7 @@ 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); + power_supply_sysfs_remove_extension(psy, ext); 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 261bf7bf6e22..9ce5eda24093 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -462,7 +462,7 @@ static struct attribute *power_supply_extension_attrs[] = { NULL }; -const struct attribute_group power_supply_extension_group = { +static const struct attribute_group power_supply_extension_group = { .name = "extensions", .attrs = power_supply_extension_attrs, }; @@ -624,3 +624,16 @@ int power_supply_charge_types_parse(unsigned int available_types, const char *bu return -EINVAL; } EXPORT_SYMBOL_GPL(power_supply_charge_types_parse); + +int power_supply_sysfs_add_extension(struct power_supply *psy, const struct power_supply_ext *ext, + struct device *dev) +{ + return sysfs_add_link_to_group(&psy->dev.kobj, power_supply_extension_group.name, + &dev->kobj, ext->name); +} + +void power_supply_sysfs_remove_extension(struct power_supply *psy, + const struct power_supply_ext *ext) +{ + sysfs_remove_link_from_group(&psy->dev.kobj, power_supply_extension_group.name, ext->name); +} -- 2.51.0 From 04d9741493e7a1d17d2109f4c9ea21f3314666b4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 15 Dec 2024 18:21:24 +0100 Subject: [PATCH 09/16] power: supply: Use power_supply_external_power_changed() in __power_supply_changed_work() The power-supply core is designed so that power-supply driver callbacks such as get_property() and external_power_changed() will not be called until the power-supply's parent driver's probe() function has completed. There is a race where power_supply_changed() can be called for a supplier of a power-supply which is being probed after the device_add() in __power_supply_register() but before the parent driver's probe() function has completed. Hitting this race breaks the power-supply core's design to not call power-supply driver callbacks before probe() completion. This problem is caused by __power_supply_changed_work() calling the external_power_changed() directly rather then going through the power_supply_external_power_changed() helper which correcly checks psy->use_cnt . Switch to using power_supply_external_power_changed() to fix this race. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20241215172133.178460-2-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index eeeed1216e22..d0bb52a7a036 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -70,10 +70,8 @@ static int __power_supply_changed_work(struct power_supply *pst, void *data) { struct power_supply *psy = data; - if (__power_supply_is_supplied_by(psy, pst)) { - if (pst->desc->external_power_changed) - pst->desc->external_power_changed(pst); - } + if (__power_supply_is_supplied_by(psy, pst)) + power_supply_external_power_changed(pst); return 0; } -- 2.51.0 From 0a219d6fd7ab89177a6fa49539def43694154d50 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Wed, 18 Dec 2024 09:35:00 +0100 Subject: [PATCH 10/16] dt-bindings: power: supply: gpio-charger: add support for default charge current limit With DT properties charge-current-limit-gpios and charge-current-limit-mapping one can define charge current limits in uA using up to 32 GPIOs. Add property charge-current-limit-default-microamp which selects a default charge current limit that must be listed in charge-current-limit-mapping. This is helpful when the smallest possible charge current limit is 0uA. The driver defaults to the smallest possible value at the moment, which disables charging on probe. With the default its possible to setup a safe charge current limit. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20241218-default-charge-current-limit-v3-1-b26118cf06b5@liebherr.com Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power/supply/gpio-charger.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/gpio-charger.yaml b/Documentation/devicetree/bindings/power/supply/gpio-charger.yaml index 89f8e2bcb2d7..25826bfc289c 100644 --- a/Documentation/devicetree/bindings/power/supply/gpio-charger.yaml +++ b/Documentation/devicetree/bindings/power/supply/gpio-charger.yaml @@ -58,6 +58,10 @@ properties: charge-current-limit-gpios property. Bit 1 second to last GPIO and so on. + charge-current-limit-default-microamp: + description: Default charge current limit. Must be listed in + charge-current-limit-mapping. + required: - compatible @@ -72,6 +76,7 @@ anyOf: dependencies: charge-current-limit-gpios: [ charge-current-limit-mapping ] charge-current-limit-mapping: [ charge-current-limit-gpios ] + charge-current-limit-default-microamp: [charge-current-limit-mapping] additionalProperties: false @@ -91,4 +96,5 @@ examples: charge-current-limit-mapping = <2500000 0x00>, // 2.5 A => both GPIOs low <700000 0x01>, // 700 mA => GPIO A.12 high <0 0x02>; // 0 mA => GPIO A.11 high + charge-current-limit-default-microamp = <700000>; }; -- 2.51.0 From de68987480cb92702eab14cf62ec2aa1bae5c867 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Wed, 18 Dec 2024 09:35:01 +0100 Subject: [PATCH 11/16] power: supply: gpio-charger: add support for default charge current limit With DT properties charge-current-limit-gpios and charge-current-limit-mapping one can define charge current limits in uA using up to 32 GPIOs. At the moment the driver defaults to smallest charge current limitation for safety reasons. When disabling charging is supported, which should be common, the driver defaults to non charging on probe. By having a default, charging can be enabled on probe for such devices. Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20241218-default-charge-current-limit-v3-2-b26118cf06b5@liebherr.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/gpio-charger.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 6139f736ecbe..46d18ce6a739 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -195,6 +195,8 @@ static int init_charge_current_limit(struct device *dev, { int i, len; u32 cur_limit = U32_MAX; + bool set_def_limit; + u32 def_limit; gpio_charger->current_limit_gpios = devm_gpiod_get_array_optional(dev, "charge-current-limit", GPIOD_OUT_LOW); @@ -228,6 +230,9 @@ static int init_charge_current_limit(struct device *dev, if (len < 0) return len; + set_def_limit = !device_property_read_u32(dev, + "charge-current-limit-default-microamp", + &def_limit); for (i=0; i < gpio_charger->current_limit_map_size; i++) { if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) { dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n"); @@ -235,8 +240,16 @@ static int init_charge_current_limit(struct device *dev, } cur_limit = gpio_charger->current_limit_map[i].limit_ua; + if (set_def_limit && def_limit == cur_limit) { + set_charge_current_limit(gpio_charger, cur_limit); + return 0; + } } + if (set_def_limit) + dev_warn(dev, "charge-current-limit-default-microamp %u not listed in charge-current-limit-mapping\n", + def_limit); + /* default to smallest current limitation for safety reasons */ len = gpio_charger->current_limit_map_size - 1; set_charge_current_limit(gpio_charger, -- 2.51.0 From db9d8eca3dc97d8d91d5e3fd878fb0bd0d8346d6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 15 Dec 2024 18:21:26 +0100 Subject: [PATCH 12/16] power: supply: ug3105_battery: Let the core handle POWER_SUPPLY_PROP_TECHNOLOGY The power-supply core already takes care of handling POWER_SUPPLY_PROP_TECHNOLOGY based on the battery_info. Drop the unnecessary handling from the driver. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20241215172133.178460-4-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ug3105_battery.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index ccc5c4d2e230..38e23bdd4603 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -287,7 +287,6 @@ out: static enum power_supply_property ug3105_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_OCV, @@ -316,9 +315,6 @@ static int ug3105_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = chip->info->technology; - break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_SYSTEM; break; -- 2.51.0 From 69a3761389cb9a3a05684e84a659c2799b62cbb3 Mon Sep 17 00:00:00 2001 From: Hardevsinh Palaniya Date: Fri, 20 Dec 2024 14:19:54 +0530 Subject: [PATCH 13/16] dt-bindings: power: supply: Add STC3117 Fuel Gauge The STC3117 provides a simple fuel gauge via I2C. Add a DT schema to describe how to set it up in the device tree. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Hardevsinh Palaniya Signed-off-by: Bhavin Sharma Link: https://lore.kernel.org/r/20241220084958.32367-2-bhavin.sharma@siliconsignals.io Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/st,stc3117.yaml | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/st,stc3117.yaml diff --git a/Documentation/devicetree/bindings/power/supply/st,stc3117.yaml b/Documentation/devicetree/bindings/power/supply/st,stc3117.yaml new file mode 100644 index 000000000000..e486131a27a9 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/st,stc3117.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/st,stc3117.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics STC3117 Fuel Gauge Unit Power Supply + +maintainers: + - Hardevsinh Palaniya + - Bhavin Sharma + +description: | + The STC3117 includes the STMicroelectronics OptimGauge algorithm. + It provides accurate battery state-of-charge (SOC) monitoring, tracks + battery parameter changes with operation conditions, temperature, + and aging, and allows the application to get a battery state-of-health + (SOH) indication. + + An alarm output signals low SOC or low voltage conditions and also + indicates fault conditions like a missing or swapped battery. + + Datasheet is available at + https://www.st.com/resource/en/datasheet/stc3117.pdf + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + enum: + - st,stc3117 + + reg: + maxItems: 1 + + monitored-battery: + description: | + The fuel gauge uses the following battery properties: + - charge-full-design-microamp-hours + - voltage-min-design-microvolt + - voltage-max-design-microvolt + + shunt-resistor-micro-ohms: + description: Current sense resistor + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - monitored-battery + - shunt-resistor-micro-ohms + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + battery@70 { + compatible = "st,stc3117"; + reg = <0x70>; + interrupt-parent = <&gpio0>; + interrupts = <31 IRQ_TYPE_LEVEL_LOW>; + monitored-battery = <&bat>; + shunt-resistor-micro-ohms = <10000>; + }; + }; -- 2.51.0 From 74e3f620852390a4e8359c6056d67af1e42b1b1c Mon Sep 17 00:00:00 2001 From: Bhavin Sharma Date: Fri, 20 Dec 2024 14:19:55 +0530 Subject: [PATCH 14/16] power: supply: Add STC3117 fuel gauge unit driver Adds initial support for the STC3117 fuel gauge. The driver provides functionality to monitor key parameters including: Voltage, Current, State of Charge (SOC), Temperature, Status Co-developed-by: Hardevsinh Palaniya Signed-off-by: Hardevsinh Palaniya Signed-off-by: Bhavin Sharma Link: https://lore.kernel.org/r/20241220084958.32367-3-bhavin.sharma@siliconsignals.io Signed-off-by: Sebastian Reichel --- MAINTAINERS | 8 + drivers/power/supply/Kconfig | 9 + drivers/power/supply/Makefile | 1 + drivers/power/supply/stc3117_fuel_gauge.c | 612 ++++++++++++++++++++++ 4 files changed, 630 insertions(+) create mode 100644 drivers/power/supply/stc3117_fuel_gauge.c diff --git a/MAINTAINERS b/MAINTAINERS index 1e930c7a58b1..6e04c83eb5cb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22165,6 +22165,14 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/st,st-mipid02.yaml F: drivers/media/i2c/st-mipid02.c +ST STC3117 FUEL GAUGE DRIVER +M: Hardevsinh Palaniya +M: Bhavin Sharma +L: linux-pm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/power/supply/st,stc3117.yaml +F: drivers/power/supply/stc3117_fuel_gauge.c + ST STM32 FIREWALL M: Gatien Chevallier S: Maintained diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 9f2eef6787f7..7b18358f194a 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -918,6 +918,15 @@ config FUEL_GAUGE_SC27XX Say Y here to enable support for fuel gauge with SC27XX PMIC chips. +config FUEL_GAUGE_STC3117 + tristate "STMicroelectronics STC3117 fuel gauge driver" + depends on CRC8 + depends on I2C + select REGMAP_I2C + help + Say Y here to enable support for fuel gauge with STC3117 + chip. + config CHARGER_UCS1002 tristate "Microchip UCS1002 USB Port Power Controller" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 59c4a9f40d28..b55cc48a4c86 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o +obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o diff --git a/drivers/power/supply/stc3117_fuel_gauge.c b/drivers/power/supply/stc3117_fuel_gauge.c new file mode 100644 index 000000000000..a1bc5970370a --- /dev/null +++ b/drivers/power/supply/stc3117_fuel_gauge.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stc3117_fuel_gauge.c - STMicroelectronics STC3117 Fuel Gauge Driver + * + * Copyright (c) 2024 Silicon Signals Pvt Ltd. + * Author: Hardevsinh Palaniya + * Bhavin Sharma + */ + +#include +#include +#include +#include +#include +#include + +#define STC3117_ADDR_MODE 0x00 +#define STC3117_ADDR_CTRL 0x01 +#define STC3117_ADDR_SOC_L 0x02 +#define STC3117_ADDR_SOC_H 0x03 +#define STC3117_ADDR_COUNTER_L 0x04 +#define STC3117_ADDR_COUNTER_H 0x05 +#define STC3117_ADDR_CURRENT_L 0x06 +#define STC3117_ADDR_CURRENT_H 0x07 +#define STC3117_ADDR_VOLTAGE_L 0x08 +#define STC3117_ADDR_VOLTAGE_H 0x09 +#define STC3117_ADDR_TEMPERATURE 0x0A +#define STC3117_ADDR_AVG_CURRENT_L 0x0B +#define STC3117_ADDR_AVG_CURRENT_H 0x0C +#define STC3117_ADDR_OCV_L 0x0D +#define STC3117_ADDR_OCV_H 0x0E +#define STC3117_ADDR_CC_CNF_L 0x0F +#define STC3117_ADDR_CC_CNF_H 0x10 +#define STC3117_ADDR_VM_CNF_L 0x11 +#define STC3117_ADDR_VM_CNF_H 0x12 +#define STC3117_ADDR_ALARM_soc 0x13 +#define STC3117_ADDR_ALARM_VOLTAGE 0x14 +#define STC3117_ADDR_ID 0x18 +#define STC3117_ADDR_CC_ADJ_L 0x1B +#define STC3117_ADDR_CC_ADJ_H 0x1C +#define STC3117_ADDR_VM_ADJ_L 0x1D +#define STC3117_ADDR_VM_ADJ_H 0x1E +#define STC3117_ADDR_RAM 0x20 +#define STC3117_ADDR_OCV_TABLE 0x30 +#define STC3117_ADDR_SOC_TABLE 0x30 + +/* Bit mask definition */ +#define STC3117_ID 0x16 +#define STC3117_MIXED_MODE 0x00 +#define STC3117_VMODE BIT(0) +#define STC3117_GG_RUN BIT(4) +#define STC3117_CC_MODE BIT(5) +#define STC3117_BATFAIL BIT(3) +#define STC3117_PORDET BIT(4) +#define STC3117_RAM_SIZE 16 +#define STC3117_OCV_TABLE_SIZE 16 +#define STC3117_RAM_TESTWORD 0x53A9 +#define STC3117_SOFT_RESET 0x11 +#define STC3117_NOMINAL_CAPACITY 2600 + +#define VOLTAGE_LSB_VALUE 9011 +#define CURRENT_LSB_VALUE 24084 +#define APP_CUTOFF_VOLTAGE 2500 +#define MAX_HRSOC 51200 +#define MAX_SOC 1000 +#define CHG_MIN_CURRENT 200 +#define CHG_END_CURRENT 20 +#define APP_MIN_CURRENT (-5) +#define BATTERY_FULL 95 +#define CRC8_POLYNOMIAL 0x07 +#define CRC8_INIT 0x00 + +DECLARE_CRC8_TABLE(stc3117_crc_table); + +enum stc3117_state { + STC3117_INIT, + STC3117_RUNNING, + STC3117_POWERDN, +}; + +/* Default ocv curve Li-ion battery */ +static const int ocv_value[16] = { + 3400, 3582, 3669, 3676, 3699, 3737, 3757, 3774, + 3804, 3844, 3936, 3984, 4028, 4131, 4246, 4320 +}; + +union stc3117_internal_ram { + u8 ram_bytes[STC3117_RAM_SIZE]; + struct { + u16 testword; /* 0-1 Bytes */ + u16 hrsoc; /* 2-3 Bytes */ + u16 cc_cnf; /* 4-5 Bytes */ + u16 vm_cnf; /* 6-7 Bytes */ + u8 soc; /* 8 Byte */ + u8 state; /* 9 Byte */ + u8 unused[5]; /* 10-14 Bytes */ + u8 crc; /* 15 Byte */ + } reg; +}; + +struct stc3117_battery_info { + int voltage_min_mv; + int voltage_max_mv; + int battery_capacity_mah; + int sense_resistor; +}; + +struct stc3117_data { + struct i2c_client *client; + struct regmap *regmap; + struct delayed_work update_work; + struct power_supply *battery; + union stc3117_internal_ram ram_data; + struct stc3117_battery_info battery_info; + + u8 soc_tab[16]; + int cc_cnf; + int vm_cnf; + int cc_adj; + int vm_adj; + int avg_current; + int avg_voltage; + int batt_current; + int voltage; + int temp; + int soc; + int ocv; + int hrsoc; + int presence; +}; + +static int stc3117_convert(int value, int factor) +{ + value = (value * factor) / 4096; + return value * 1000; +} + +static int stc3117_get_battery_data(struct stc3117_data *data) +{ + u8 reg_list[16]; + u8 data_adjust[4]; + int value, mode; + + regmap_bulk_read(data->regmap, STC3117_ADDR_MODE, + reg_list, sizeof(reg_list)); + + /* soc */ + value = (reg_list[3] << 8) + reg_list[2]; + data->hrsoc = value; + data->soc = (value * 10 + 256) / 512; + + /* current in uA*/ + value = (reg_list[7] << 8) + reg_list[6]; + data->batt_current = stc3117_convert(value, + CURRENT_LSB_VALUE / data->battery_info.sense_resistor); + + /* voltage in uV */ + value = (reg_list[9] << 8) + reg_list[8]; + data->voltage = stc3117_convert(value, VOLTAGE_LSB_VALUE); + + /* temp in 1/10 °C */ + data->temp = reg_list[10] * 10; + + /* Avg current in uA */ + value = (reg_list[12] << 8) + reg_list[11]; + regmap_read(data->regmap, STC3117_ADDR_MODE, &mode); + if (!(mode & STC3117_VMODE)) { + value = stc3117_convert(value, + CURRENT_LSB_VALUE / data->battery_info.sense_resistor); + value = value / 4; + } else { + value = stc3117_convert(value, 36 * STC3117_NOMINAL_CAPACITY); + } + data->avg_current = value; + + /* ocv in uV */ + value = (reg_list[14] << 8) + reg_list[13]; + value = stc3117_convert(value, VOLTAGE_LSB_VALUE); + value = (value + 2) / 4; + data->ocv = value; + + /* CC & VM adjustment counters */ + regmap_bulk_read(data->regmap, STC3117_ADDR_CC_ADJ_L, + data_adjust, sizeof(data_adjust)); + value = (data_adjust[1] << 8) + data_adjust[0]; + data->cc_adj = value; + + value = (data_adjust[3] << 8) + data_adjust[2]; + data->vm_adj = value; + + return 0; +} + +static int ram_write(struct stc3117_data *data) +{ + int ret; + + ret = regmap_bulk_write(data->regmap, STC3117_ADDR_RAM, + data->ram_data.ram_bytes, STC3117_RAM_SIZE); + if (ret) + return ret; + + return 0; +}; + +static int ram_read(struct stc3117_data *data) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, STC3117_ADDR_RAM, + data->ram_data.ram_bytes, STC3117_RAM_SIZE); + if (ret) + return ret; + + return 0; +}; + +static int stc3117_set_para(struct stc3117_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, STC3117_ADDR_MODE, STC3117_VMODE); + + for (int i = 0; i < STC3117_OCV_TABLE_SIZE; i++) + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_TABLE + i, + ocv_value[i] * 100 / 55); + if (data->soc_tab[1] != 0) + ret |= regmap_bulk_write(data->regmap, STC3117_ADDR_SOC_TABLE, + data->soc_tab, STC3117_OCV_TABLE_SIZE); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CC_CNF_H, + (data->ram_data.reg.cc_cnf >> 8) & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CC_CNF_L, + data->ram_data.reg.cc_cnf & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_VM_CNF_H, + (data->ram_data.reg.vm_cnf >> 8) & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_VM_CNF_L, + data->ram_data.reg.vm_cnf & 0xFF); + + ret |= regmap_write(data->regmap, STC3117_ADDR_CTRL, 0x03); + + ret |= regmap_write(data->regmap, STC3117_ADDR_MODE, + STC3117_MIXED_MODE | STC3117_GG_RUN); + + return ret; +}; + +static int stc3117_init(struct stc3117_data *data) +{ + int id, ret; + int ctrl; + int ocv_m, ocv_l; + + regmap_read(data->regmap, STC3117_ADDR_ID, &id); + if (id != STC3117_ID) + return -EINVAL; + + data->cc_cnf = (data->battery_info.battery_capacity_mah * + data->battery_info.sense_resistor * 250 + 6194) / 12389; + data->vm_cnf = (data->battery_info.battery_capacity_mah + * 200 * 50 + 24444) / 48889; + + /* Battery has not been removed */ + data->presence = 1; + + /* Read RAM data */ + ret = ram_read(data); + if (ret) + return ret; + + if (data->ram_data.reg.testword != STC3117_RAM_TESTWORD || + (crc8(stc3117_crc_table, data->ram_data.ram_bytes, + STC3117_RAM_SIZE, CRC8_INIT)) != 0) { + data->ram_data.reg.testword = STC3117_RAM_TESTWORD; + data->ram_data.reg.cc_cnf = data->cc_cnf; + data->ram_data.reg.vm_cnf = data->vm_cnf; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + + ret = regmap_read(data->regmap, STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } else { + ret = regmap_read(data->regmap, STC3117_ADDR_CTRL, &ctrl); + if (ret) + return ret; + + if ((ctrl & STC3117_BATFAIL) != 0 || + (ctrl & STC3117_PORDET) != 0) { + ret = regmap_read(data->regmap, + STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, + STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, + STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, + STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } else { + ret = stc3117_set_para(data); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_H, + (data->ram_data.reg.hrsoc >> 8 & 0xFF)); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_L, + (data->ram_data.reg.hrsoc & 0xFF)); + if (ret) + return ret; + } + } + + data->ram_data.reg.state = STC3117_INIT; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + ret = ram_write(data); + if (ret) + return ret; + + return 0; +}; + +static int stc3117_task(struct stc3117_data *data) +{ + int id, mode, ret; + int count_l, count_m; + int ocv_l, ocv_m; + + regmap_read(data->regmap, STC3117_ADDR_ID, &id); + if (id != STC3117_ID) { + data->presence = 0; + return -EINVAL; + } + + stc3117_get_battery_data(data); + + /* Read RAM data */ + ret = ram_read(data); + if (ret) + return ret; + + if (data->ram_data.reg.testword != STC3117_RAM_TESTWORD || + (crc8(stc3117_crc_table, data->ram_data.ram_bytes, + STC3117_RAM_SIZE, CRC8_INIT) != 0)) { + data->ram_data.reg.testword = STC3117_RAM_TESTWORD; + data->ram_data.reg.cc_cnf = data->cc_cnf; + data->ram_data.reg.vm_cnf = data->vm_cnf; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + data->ram_data.reg.state = STC3117_INIT; + } + + /* check battery presence status */ + ret = regmap_read(data->regmap, STC3117_ADDR_CTRL, &mode); + if ((mode & STC3117_BATFAIL) != 0) { + data->presence = 0; + data->ram_data.reg.testword = 0; + data->ram_data.reg.state = STC3117_INIT; + ret = ram_write(data); + ret |= regmap_write(data->regmap, STC3117_ADDR_CTRL, STC3117_PORDET); + if (ret) + return ret; + } + + data->presence = 1; + + ret = regmap_read(data->regmap, STC3117_ADDR_MODE, &mode); + if (ret) + return ret; + if ((mode & STC3117_GG_RUN) == 0) { + if (data->ram_data.reg.state > STC3117_INIT) { + ret = stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_H, + (data->ram_data.reg.hrsoc >> 8 & 0xFF)); + ret |= regmap_write(data->regmap, STC3117_ADDR_SOC_L, + (data->ram_data.reg.hrsoc & 0xFF)); + if (ret) + return ret; + } else { + ret = regmap_read(data->regmap, STC3117_ADDR_OCV_H, &ocv_m); + + ret |= regmap_read(data->regmap, STC3117_ADDR_OCV_L, &ocv_l); + + ret |= stc3117_set_para(data); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_H, ocv_m); + + ret |= regmap_write(data->regmap, STC3117_ADDR_OCV_L, ocv_l); + if (ret) + return ret; + } + data->ram_data.reg.state = STC3117_INIT; + } + + regmap_read(data->regmap, STC3117_ADDR_COUNTER_L, &count_l); + regmap_read(data->regmap, STC3117_ADDR_COUNTER_H, &count_m); + + count_m = (count_m << 8) + count_l; + + /* INIT state, wait for batt_current & temperature value available: */ + if (data->ram_data.reg.state == STC3117_INIT && count_m > 4) { + data->avg_voltage = data->voltage; + data->avg_current = data->batt_current; + data->ram_data.reg.state = STC3117_RUNNING; + } + + if (data->ram_data.reg.state != STC3117_RUNNING) { + data->batt_current = -ENODATA; + data->temp = -ENODATA; + } else { + if (data->voltage < APP_CUTOFF_VOLTAGE) + data->soc = -ENODATA; + + if (mode & STC3117_VMODE) { + data->avg_current = -ENODATA; + data->batt_current = -ENODATA; + } + } + + data->ram_data.reg.hrsoc = data->hrsoc; + data->ram_data.reg.soc = (data->soc + 5) / 10; + data->ram_data.reg.crc = crc8(stc3117_crc_table, + data->ram_data.ram_bytes, + STC3117_RAM_SIZE - 1, CRC8_INIT); + + ret = ram_write(data); + if (ret) + return ret; + return 0; +}; + +static void fuel_gauge_update_work(struct work_struct *work) +{ + struct stc3117_data *data = + container_of(work, struct stc3117_data, update_work.work); + + stc3117_task(data); + + /* Schedule the work to run again in 2 seconds */ + schedule_delayed_work(&data->update_work, msecs_to_jiffies(2000)); +} + +static int stc3117_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct stc3117_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (data->soc > BATTERY_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (data->batt_current < 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (data->batt_current > 0) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = data->voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = data->batt_current; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = data->ocv; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = data->avg_current; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = data->soc; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = data->temp; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = data->presence; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property stc3117_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_PRESENT, +}; + +static const struct power_supply_desc stc3117_battery_desc = { + .name = "stc3117-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = stc3117_get_property, + .properties = stc3117_battery_props, + .num_properties = ARRAY_SIZE(stc3117_battery_props), +}; + +static const struct regmap_config stc3117_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int stc3117_probe(struct i2c_client *client) +{ + struct stc3117_data *data; + struct power_supply_config psy_cfg = {}; + struct power_supply_battery_info *info; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->regmap = devm_regmap_init_i2c(client, &stc3117_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + psy_cfg.drv_data = data; + psy_cfg.fwnode = dev_fwnode(&client->dev); + + crc8_populate_msb(stc3117_crc_table, CRC8_POLYNOMIAL); + + data->battery = devm_power_supply_register(&client->dev, + &stc3117_battery_desc, &psy_cfg); + if (IS_ERR(data->battery)) + return dev_err_probe(&client->dev, PTR_ERR(data->battery), + "failed to register battery\n"); + + ret = device_property_read_u32(&client->dev, "shunt-resistor-micro-ohms", + &data->battery_info.sense_resistor); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get shunt-resistor-micro-ohms\n"); + data->battery_info.sense_resistor = data->battery_info.sense_resistor / 1000; + + ret = power_supply_get_battery_info(data->battery, &info); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get battery information\n"); + + data->battery_info.battery_capacity_mah = info->charge_full_design_uah / 1000; + data->battery_info.voltage_min_mv = info->voltage_min_design_uv / 1000; + data->battery_info.voltage_max_mv = info->voltage_max_design_uv / 1000; + + ret = stc3117_init(data); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to initialize of stc3117\n"); + + ret = devm_delayed_work_autocancel(&client->dev, &data->update_work, + fuel_gauge_update_work); + if (ret) + return ret; + + schedule_delayed_work(&data->update_work, 0); + + return 0; +} + +static const struct i2c_device_id stc3117_id[] = { + { "stc3117", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stc3117_id); + +static const struct of_device_id stc3117_of_match[] = { + { .compatible = "st,stc3117" }, + { } +}; +MODULE_DEVICE_TABLE(of, stc3117_of_match); + +static struct i2c_driver stc3117_i2c_driver = { + .driver = { + .name = "stc3117_i2c_driver", + .of_match_table = stc3117_of_match, + }, + .probe = stc3117_probe, + .id_table = stc3117_id, +}; + +module_i2c_driver(stc3117_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hardevsinh Palaniya "); +MODULE_AUTHOR("Bhavin Sharma "); +MODULE_DESCRIPTION("STC3117 Fuel Gauge Driver"); -- 2.51.0 From f6945d52ee5346900fb7251ec23d34822a06918e Mon Sep 17 00:00:00 2001 From: "Sicelo A. Mhlongo" Date: Sat, 21 Dec 2024 18:10:23 +0200 Subject: [PATCH 15/16] power: supply: bq2415x_charger: Immediately reschedule delayed work on notifier events When the notifier is called we want to schedule the worker as soon as possible. Thus it makes sense to reschedule any waiting work and only queue a new one if there is none. Suggested-by: Ivaylo Dimitrov Signed-off-by: Sicelo A. Mhlongo Link: https://lore.kernel.org/r/20241221161124.114989-1-absicsz@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq2415x_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c index 18a6c3dbc758..22f6a3b71632 100644 --- a/drivers/power/supply/bq2415x_charger.c +++ b/drivers/power/supply/bq2415x_charger.c @@ -842,7 +842,7 @@ static int bq2415x_notifier_call(struct notifier_block *nb, if (bq->automode < 1) return NOTIFY_OK; - schedule_delayed_work(&bq->work, 0); + mod_delayed_work(system_wq, &bq->work, 0); return NOTIFY_OK; } -- 2.51.0 From a3a8799165ff83bb764fd800c6559c3cba0ddac3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 21 Dec 2024 13:51:40 +0100 Subject: [PATCH 16/16] platform/x86: dell-laptop: Use power_supply_charge_types_show/_parse() helpers MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Make battery_modes a map between tokens and enum power_supply_charge_type values instead of between tokens and strings and use the new power_supply_charge_types_show/_parse() helpers for show()/store() to ensure that things are handled in the same way as in other drivers. This also changes battery_supported_modes to be a bitmap of charge-types (enum power_supply_charge_type values) rather then a bitmap of indices into battery_modes[]. Reviewed-by: Thomas Weißschuh Signed-off-by: Hans de Goede Acked-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20241221125140.345776-2-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/platform/x86/dell/dell-laptop.c | 54 ++++++++++++------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c index 5671bd0deee7..e6da468daf83 100644 --- a/drivers/platform/x86/dell/dell-laptop.c +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -103,15 +103,15 @@ static bool mute_led_registered; struct battery_mode_info { int token; - const char *label; + enum power_supply_charge_type charge_type; }; static const struct battery_mode_info battery_modes[] = { - { BAT_PRI_AC_MODE_TOKEN, "Trickle" }, - { BAT_EXPRESS_MODE_TOKEN, "Fast" }, - { BAT_STANDARD_MODE_TOKEN, "Standard" }, - { BAT_ADAPTIVE_MODE_TOKEN, "Adaptive" }, - { BAT_CUSTOM_MODE_TOKEN, "Custom" }, + { BAT_PRI_AC_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_TRICKLE }, + { BAT_EXPRESS_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_FAST }, + { BAT_STANDARD_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_STANDARD }, + { BAT_ADAPTIVE_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE }, + { BAT_CUSTOM_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_CUSTOM }, }; static u32 battery_supported_modes; @@ -2261,46 +2261,42 @@ static ssize_t charge_types_show(struct device *dev, struct device_attribute *attr, char *buf) { - ssize_t count = 0; + enum power_supply_charge_type charge_type; int i; for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { - bool active; + charge_type = battery_modes[i].charge_type; - if (!(battery_supported_modes & BIT(i))) + if (!(battery_supported_modes & BIT(charge_type))) continue; - active = dell_battery_mode_is_active(battery_modes[i].token); - count += sysfs_emit_at(buf, count, active ? "[%s] " : "%s ", - battery_modes[i].label); - } + if (!dell_battery_mode_is_active(battery_modes[i].token)) + continue; - /* convert the last space to a newline */ - if (count > 0) - count--; - count += sysfs_emit_at(buf, count, "\n"); + return power_supply_charge_types_show(dev, battery_supported_modes, + charge_type, buf); + } - return count; + /* No active mode found */ + return -EIO; } static ssize_t charge_types_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { - bool matched = false; - int err, i; + int charge_type, err, i; - for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { - if (!(battery_supported_modes & BIT(i))) - continue; + charge_type = power_supply_charge_types_parse(battery_supported_modes, buf); + if (charge_type < 0) + return charge_type; - if (sysfs_streq(battery_modes[i].label, buf)) { - matched = true; + for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { + if (battery_modes[i].charge_type == charge_type) break; - } } - if (!matched) - return -EINVAL; + if (i == ARRAY_SIZE(battery_modes)) + return -ENOENT; err = dell_battery_set_mode(battery_modes[i].token); if (err) @@ -2430,7 +2426,7 @@ static u32 __init battery_get_supported_modes(void) for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { if (dell_smbios_find_token(battery_modes[i].token)) - modes |= BIT(i); + modes |= BIT(battery_modes[i].charge_type); } return modes; -- 2.51.0