* [PATCH] power: supply: bq27xxx: add model name support for bq27z561
@ 2026-08-14 14:05 Alexander Svarvare
2026-09-18 8:49 ` Henrik Grimler
0 siblings, 1 reply; 2+ messages in thread
From: Alexander Svarvare @ 2026-08-14 14:05 UTC (permalink / raw)
To: Sebastian Reichel, Pali Rohár
Cc: Waqar Hameed, Henrik Grimler, Alexander Svarvare, linux-pm, linux-kernel
The bq27z561 fuel gauge can report its device name via the
AltManufacturerAccess() (MAC) command 0x004A. Expose this information
through the POWER_SUPPLY_PROP_MODEL_NAME property.
This allows userspace charge-management daemons to make more intelligent
decisions by being able to map battery pack to datasheet and infer
additional useful battery characteristics.
Other power_supply properties can be provided through
AltManufacturerAccess(), so implement an interface. Make it
modular/flexible since for other chips, the same MAC-command
for retrieving a property may have a different numerical value.
Serialize the complete MAC write-delay-read transaction for concurrency
safety, preventing another command from replacing the pending response
during the required delay. Keep dedicated per-device storage for the model
name to preserve the returned pointer’s lifetime.
Hardware-tested on BQ27Z561 using a backport to the product kernel.
The model_name property returned the expected value.
Signed-off-by: Alexander Svarvare <alexander.svarvare@axis.com>
---
bq27xxx fuel gauges (FGs) provide most of power_supply properties by basic
register reads. For the remaining properties, they can be read by writing a
command to a buffer AltManufacturerAccess() and reading the result from
another buffer MACData().
Since the release of bq27z561, six bq27xxx 1-cell FGs have been released.
Three support reading the model_name in the same way as bq27z561 while five
provide properties supported by power_supply.h through
AltManufacturerAccess(). In addition, five specify the return string as
2 bytes echo of the MAC command followed by the string. This format has
therefore been hardcoded.
*Some* properties (varies) provided in this manner which are supported in
power_supply.h are:
model_name, manufacturer, serial_number, manufacture day/month/year
---
drivers/power/supply/bq27xxx_battery.c | 95 ++++++++++++++++++++++++++++++++--
include/linux/power/bq27xxx_battery.h | 5 ++
2 files changed, 97 insertions(+), 3 deletions(-)
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 45f0e39b8c2d..2a24eaf1aecb 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -93,6 +93,9 @@
#define INVALID_REG_ADDR 0xff
+/* SMBus block transfers are limited to 32 bytes. */
+#define BQ27XXX_DATA_BLOCK_LEN 32
+
/*
* bq27xxx_reg_index - Register names
*
@@ -833,6 +836,7 @@ static enum power_supply_property bq27z561_props[] = {
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
@@ -1010,16 +1014,39 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = {
#define BQ27XXX_DATA(ref, key, opt) { \
.opts = (opt), \
- .unseal_key = key, \
+ .unseal_key = (key), \
+ .cmds = &bq27xxx_no_cmds, \
+ .regs = ref##_regs, \
+ .dm_regs = ref##_dm_regs, \
+ .props = ref##_props, \
+ .props_size = ARRAY_SIZE(ref##_props), }
+
+#define BQ27XXX_CMDS(ref, key, opt, cptr) { \
+ .opts = (opt), \
+ .unseal_key = (key), \
+ .cmds = (cptr), \
.regs = ref##_regs, \
.dm_regs = ref##_dm_regs, \
.props = ref##_props, \
- .props_size = ARRAY_SIZE(ref##_props) }
+ .props_size = ARRAY_SIZE(ref##_props), }
+
+struct bq27xxx_cmds {
+ u16 model_name_cmd;
+};
+
+static const struct bq27xxx_cmds bq27xxx_no_cmds = {
+ .model_name_cmd = 0,
+};
+
+static const struct bq27xxx_cmds bq27z561_cmds = {
+ .model_name_cmd = 0x004A,
+};
static struct {
u32 opts;
u32 unseal_key;
u8 *regs;
+ const struct bq27xxx_cmds *cmds;
struct bq27xxx_dm_reg *dm_regs;
enum power_supply_property *props;
size_t props_size;
@@ -1051,7 +1078,7 @@ static struct {
[BQ27426] = BQ27XXX_DATA(bq27426, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
[BQ27441] = BQ27XXX_DATA(bq27441, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
[BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
- [BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS),
+ [BQ27Z561] = BQ27XXX_CMDS(bq27z561, 0 , BQ27Z561_O_BITS, &bq27z561_cmds),
[BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS),
[BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \
BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM),
@@ -1220,6 +1247,60 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
return ret;
}
+static int bq27xxx_mac_read_string(struct bq27xxx_device_info *di, u16 cmd,
+ char *name_buffer, u8 buf_len)
+{
+ u8 mac_data[BQ27XXX_DATA_BLOCK_LEN + 1];
+ int ret;
+
+ if (!buf_len)
+ return -EINVAL;
+
+ /* Serialize the complete MAC command-response transaction. */
+ guard(mutex)(&di->lock);
+
+ ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, cmd, false);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to issue MAC command: %d\n", ret);
+ return ret;
+ }
+
+ /* 66-us AltManufacturerAccess() wait time, use 1 ms guard */
+ BQ27XXX_MSLEEP(1);
+
+ ret = bq27xxx_read_block(di, BQ27XXX_DM_CLASS, mac_data,
+ BQ27XXX_DATA_BLOCK_LEN);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to read MAC data block: %d\n", ret);
+ return ret;
+ }
+
+ if (mac_data[0] != (cmd & 0xff) ||
+ mac_data[1] != (cmd >> 8))
+ return -EPROTO;
+
+ if (!mac_data[2])
+ return -ENODATA;
+
+ mac_data[BQ27XXX_DATA_BLOCK_LEN] = '\0';
+ ret = strscpy(name_buffer, &mac_data[2], buf_len);
+
+ if (ret < 0) {
+ dev_err(di->dev, "failed to copy MAC string: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bq27xxx_get_model_name(struct bq27xxx_device_info *di, char *name, u8 len)
+{
+ if (!di->cmds || !di->cmds->model_name_cmd)
+ return -EINVAL;
+
+ return bq27xxx_mac_read_string(di, di->cmds->model_name_cmd, name, len);
+}
+
static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
{
int ret;
@@ -2206,6 +2287,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_HEALTH:
ret = bq27xxx_battery_read_health(di, val);
break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ ret = bq27xxx_get_model_name(di, di->model_name_buf, sizeof(di->model_name_buf));
+ if (ret < 0)
+ break;
+
+ val->strval = di->model_name_buf;
+ break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ27XXX_MANUFACTURER;
break;
@@ -2243,6 +2331,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
di->unseal_key = bq27xxx_chip_data[di->chip].unseal_key;
di->dm_regs = bq27xxx_chip_data[di->chip].dm_regs;
di->opts = bq27xxx_chip_data[di->chip].opts;
+ di->cmds = bq27xxx_chip_data[di->chip].cmds;
psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL);
if (!psy_desc)
diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
index d56e1276aafe..009ccdd6395a 100644
--- a/include/linux/power/bq27xxx_battery.h
+++ b/include/linux/power/bq27xxx_battery.h
@@ -4,6 +4,9 @@
#include <linux/power_supply.h>
+/* S21 Device Name holds up to 20 characters; reserve one byte for NUL. */
+#define BQ27XXX_STR_BUF_LEN 21
+
enum bq27xxx_chip {
BQ27000 = 1, /* bq27000, bq27200 */
BQ27010, /* bq27010, bq27210 */
@@ -58,6 +61,8 @@ struct bq27xxx_device_info {
const char *name;
struct bq27xxx_dm_reg *dm_regs;
u32 unseal_key;
+ const struct bq27xxx_cmds *cmds;
+ char model_name_buf[BQ27XXX_STR_BUF_LEN];
struct bq27xxx_access_methods bus;
struct bq27xxx_reg_cache cache;
int charge_design_full;
---
base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
change-id: 20260625-alexander-laddchip-patch-4b565f9844cb
Best regards,
--
Alexander Svarvare <alexander.svarvare@axis.com>
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH] power: supply: bq27xxx: add model name support for bq27z561
2026-08-14 14:05 [PATCH] power: supply: bq27xxx: add model name support for bq27z561 Alexander Svarvare
@ 2026-09-18 8:49 ` Henrik Grimler
0 siblings, 0 replies; 2+ messages in thread
From: Henrik Grimler @ 2026-09-18 8:49 UTC (permalink / raw)
To: Alexander Svarvare
Cc: Sebastian Reichel, Pali Rohár, Waqar Hameed, linux-pm,
linux-kernel, kernel
Hi Alexander,
I think we can take this patch for another round internally, please
see comments. Sashiko also had three concerns that seem valid to me,
please have a look.
On Fri, Aug 14, 2026 at 04:05:58PM +0200, Alexander Svarvare wrote:
> The bq27z561 fuel gauge can report its device name via the
> AltManufacturerAccess() (MAC) command 0x004A. Expose this information
> through the POWER_SUPPLY_PROP_MODEL_NAME property.
>
> This allows userspace charge-management daemons to make more intelligent
> decisions by being able to map battery pack to datasheet and infer
> additional useful battery characteristics.
>
> Other power_supply properties can be provided through
> AltManufacturerAccess(), so implement an interface. Make it
> modular/flexible since for other chips, the same MAC-command
> for retrieving a property may have a different numerical value.
>
> Serialize the complete MAC write-delay-read transaction for concurrency
> safety, preventing another command from replacing the pending response
> during the required delay. Keep dedicated per-device storage for the model
> name to preserve the returned pointer’s lifetime.
>
> Hardware-tested on BQ27Z561 using a backport to the product kernel.
>
> The model_name property returned the expected value.
Great that it is tested, and worked, with the backported patch! It
would be good to test with a close-to-mainline kernel as well, as
there might very well be some regression elsewhere that affects the
patch or driver. Let's talk off-list for how such tests can most
easily be done.
> Signed-off-by: Alexander Svarvare <alexander.svarvare@axis.com>
> ---
> bq27xxx fuel gauges (FGs) provide most of power_supply properties by basic
> register reads. For the remaining properties, they can be read by writing a
> command to a buffer AltManufacturerAccess() and reading the result from
> another buffer MACData().
>
> Since the release of bq27z561, six bq27xxx 1-cell FGs have been released.
> Three support reading the model_name in the same way as bq27z561 while five
> provide properties supported by power_supply.h through
> AltManufacturerAccess(). In addition, five specify the return string as
> 2 bytes echo of the MAC command followed by the string. This format has
> therefore been hardcoded.
>
> *Some* properties (varies) provided in this manner which are supported in
> power_supply.h are:
> model_name, manufacturer, serial_number, manufacture day/month/year
> ---
> drivers/power/supply/bq27xxx_battery.c | 95 ++++++++++++++++++++++++++++++++--
> include/linux/power/bq27xxx_battery.h | 5 ++
> 2 files changed, 97 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
> index 45f0e39b8c2d..2a24eaf1aecb 100644
> --- a/drivers/power/supply/bq27xxx_battery.c
> +++ b/drivers/power/supply/bq27xxx_battery.c
> @@ -93,6 +93,9 @@
>
> #define INVALID_REG_ADDR 0xff
>
> +/* SMBus block transfers are limited to 32 bytes. */
> +#define BQ27XXX_DATA_BLOCK_LEN 32
> +
> /*
> * bq27xxx_reg_index - Register names
> *
> @@ -833,6 +836,7 @@ static enum power_supply_property bq27z561_props[] = {
> POWER_SUPPLY_PROP_CYCLE_COUNT,
> POWER_SUPPLY_PROP_POWER_AVG,
> POWER_SUPPLY_PROP_HEALTH,
> + POWER_SUPPLY_PROP_MODEL_NAME,
> POWER_SUPPLY_PROP_MANUFACTURER,
> };
>
> @@ -1010,16 +1014,39 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = {
>
> #define BQ27XXX_DATA(ref, key, opt) { \
> .opts = (opt), \
> - .unseal_key = key, \
> + .unseal_key = (key), \
> + .cmds = &bq27xxx_no_cmds, \
> + .regs = ref##_regs, \
> + .dm_regs = ref##_dm_regs, \
> + .props = ref##_props, \
> + .props_size = ARRAY_SIZE(ref##_props), }
> +
> +#define BQ27XXX_CMDS(ref, key, opt, cptr) { \
> + .opts = (opt), \
> + .unseal_key = (key), \
> + .cmds = (cptr), \
> .regs = ref##_regs, \
> .dm_regs = ref##_dm_regs, \
> .props = ref##_props, \
> - .props_size = ARRAY_SIZE(ref##_props) }
> + .props_size = ARRAY_SIZE(ref##_props), }
> +
> +struct bq27xxx_cmds {
> + u16 model_name_cmd;
> +};
> +
> +static const struct bq27xxx_cmds bq27xxx_no_cmds = {
> + .model_name_cmd = 0,
> +};
Rather than having one struct with all 0 u16's we can use bits in the
opts variable (and save some memory) to indicate that a command is
supported. We could add something like:
#define BQ27XXX_O_MODEL_NAME_CMD BIT(9)
and check `di->opts & BQ27XXX_O_MODEL_NAME_CMD`.
I looked through the reference manuals for quite a few of the bq27
chips, and all that have Device Name among the ManufacturerAccess regs
seem to use 0x004A, so should be safe to always use that command for
that opt bit (if some future bq chip has another command then it could
use another opts bit).
Best regards,
Henrik Grimler
> +static const struct bq27xxx_cmds bq27z561_cmds = {
> + .model_name_cmd = 0x004A,
> +};
>
> static struct {
> u32 opts;
> u32 unseal_key;
> u8 *regs;
> + const struct bq27xxx_cmds *cmds;
> struct bq27xxx_dm_reg *dm_regs;
> enum power_supply_property *props;
> size_t props_size;
> @@ -1051,7 +1078,7 @@ static struct {
> [BQ27426] = BQ27XXX_DATA(bq27426, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
> [BQ27441] = BQ27XXX_DATA(bq27441, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
> [BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
> - [BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS),
> + [BQ27Z561] = BQ27XXX_CMDS(bq27z561, 0 , BQ27Z561_O_BITS, &bq27z561_cmds),
> [BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS),
> [BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \
> BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM),
> @@ -1220,6 +1247,60 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
> return ret;
> }
>
> +static int bq27xxx_mac_read_string(struct bq27xxx_device_info *di, u16 cmd,
> + char *name_buffer, u8 buf_len)
> +{
> + u8 mac_data[BQ27XXX_DATA_BLOCK_LEN + 1];
> + int ret;
> +
> + if (!buf_len)
> + return -EINVAL;
> +
> + /* Serialize the complete MAC command-response transaction. */
> + guard(mutex)(&di->lock);
> +
> + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, cmd, false);
> + if (ret < 0) {
> + dev_err(di->dev, "failed to issue MAC command: %d\n", ret);
> + return ret;
> + }
> +
> + /* 66-us AltManufacturerAccess() wait time, use 1 ms guard */
> + BQ27XXX_MSLEEP(1);
> +
> + ret = bq27xxx_read_block(di, BQ27XXX_DM_CLASS, mac_data,
> + BQ27XXX_DATA_BLOCK_LEN);
> + if (ret < 0) {
> + dev_err(di->dev, "failed to read MAC data block: %d\n", ret);
> + return ret;
> + }
> +
> + if (mac_data[0] != (cmd & 0xff) ||
> + mac_data[1] != (cmd >> 8))
> + return -EPROTO;
> +
> + if (!mac_data[2])
> + return -ENODATA;
> +
> + mac_data[BQ27XXX_DATA_BLOCK_LEN] = '\0';
> + ret = strscpy(name_buffer, &mac_data[2], buf_len);
> +
> + if (ret < 0) {
> + dev_err(di->dev, "failed to copy MAC string: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int bq27xxx_get_model_name(struct bq27xxx_device_info *di, char *name, u8 len)
> +{
> + if (!di->cmds || !di->cmds->model_name_cmd)
> + return -EINVAL;
> +
> + return bq27xxx_mac_read_string(di, di->cmds->model_name_cmd, name, len);
> +}
> +
> static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
> {
> int ret;
> @@ -2206,6 +2287,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
> case POWER_SUPPLY_PROP_HEALTH:
> ret = bq27xxx_battery_read_health(di, val);
> break;
> + case POWER_SUPPLY_PROP_MODEL_NAME:
> + ret = bq27xxx_get_model_name(di, di->model_name_buf, sizeof(di->model_name_buf));
> + if (ret < 0)
> + break;
> +
> + val->strval = di->model_name_buf;
> + break;
> case POWER_SUPPLY_PROP_MANUFACTURER:
> val->strval = BQ27XXX_MANUFACTURER;
> break;
> @@ -2243,6 +2331,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
> di->unseal_key = bq27xxx_chip_data[di->chip].unseal_key;
> di->dm_regs = bq27xxx_chip_data[di->chip].dm_regs;
> di->opts = bq27xxx_chip_data[di->chip].opts;
> + di->cmds = bq27xxx_chip_data[di->chip].cmds;
>
> psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL);
> if (!psy_desc)
> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
> index d56e1276aafe..009ccdd6395a 100644
> --- a/include/linux/power/bq27xxx_battery.h
> +++ b/include/linux/power/bq27xxx_battery.h
> @@ -4,6 +4,9 @@
>
> #include <linux/power_supply.h>
>
> +/* S21 Device Name holds up to 20 characters; reserve one byte for NUL. */
> +#define BQ27XXX_STR_BUF_LEN 21
> +
> enum bq27xxx_chip {
> BQ27000 = 1, /* bq27000, bq27200 */
> BQ27010, /* bq27010, bq27210 */
> @@ -58,6 +61,8 @@ struct bq27xxx_device_info {
> const char *name;
> struct bq27xxx_dm_reg *dm_regs;
> u32 unseal_key;
> + const struct bq27xxx_cmds *cmds;
> + char model_name_buf[BQ27XXX_STR_BUF_LEN];
> struct bq27xxx_access_methods bus;
> struct bq27xxx_reg_cache cache;
> int charge_design_full;
>
> ---
> base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
> change-id: 20260625-alexander-laddchip-patch-4b565f9844cb
>
> Best regards,
> --
> Alexander Svarvare <alexander.svarvare@axis.com>
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-18 8:49 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-14 14:05 [PATCH] power: supply: bq27xxx: add model name support for bq27z561 Alexander Svarvare
2026-09-18 8:49 ` Henrik Grimler
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®