[PATCH] power: supply: bq27xxx: add model name support for bq27z561

Alexander Svarvare <[email protected]>
Newsgroups org.kernel.vger.linux-pm,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
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 <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.