Skip to contentSkip to conversation

bq27xxx_battery partial series

PATCH New Liam Breck 2 participants 15 messages Mar 29, 2017

Subsystems (3)
lore
Share & tools
lore

Liam Breck

author
#0 lore raw

cover bq27xxx_battery partial series

All comments from previous partial series were resolved.

power: bq27xxx_battery: Add bulk transfer bus methods
power: bq27xxx_battery: Add chip data memory read/write support
power: bq27xxx_battery: Add power_supply_battery_info support
power: bq27xxx_battery: Enable chip data memory update for certain chips
power: bq27xxx_battery: Remove duplicate register arrays

 drivers/power/supply/bq27xxx_battery.c     | 694 +++++++++++++++++++++++------
 drivers/power/supply/bq27xxx_battery_i2c.c |  98 +++-
 include/linux/power/bq27xxx_battery.h      |  26 +-
 3 files changed, 676 insertions(+), 142 deletions(-)
Signature · 1 line
2.9.3
author
#1 lore raw

PATCH 5/9 power: bq27xxx_battery: Add bulk transfer bus methods

From: Matt Ranostay <[email protected]>

Declare bus.write/read_bulk/write_bulk().
Add I2C write/read_bulk/write_bulk() to implement the above.
Add bq27xxx_write/read_block/write_block() helper functions to call the above.

---
 drivers/power/supply/bq27xxx_battery.c     | 54 +++++++++++++++++++-
 drivers/power/supply/bq27xxx_battery_i2c.c | 82 +++++++++++++++++++++++++++++-
 include/linux/power/bq27xxx_battery.h      |  3 ++
 3 files changed, 136 insertions(+), 3 deletions(-)
drivers/power/supply/bq27xxx_battery.c +52 -2
index 398801a..5b6c1b2 100644
@@ -791,14 +791,64 @@ MODULE_PARM_DESC(poll_interval,
  * Common code for BQ27xxx devices
  */
 
+#define BQ27XXX_REG_ERR(e,s,i) \
+	if ((e) < 0) dev_dbg(di->dev, "failed to " s " register 0x%02x (index %d)\n", \
+	                     di->regs[i], (i))
+
 static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
 			       bool single)
 {
-	/* Reports EINVAL for invalid/missing registers */
+	int ret;
+
 	if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
 		return -EINVAL;
 
-	return di->bus.read(di, di->regs[reg_index], single);
+	ret = di->bus.read(di, di->regs[reg_index], single);
+	BQ27XXX_REG_ERR(ret, "read", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index,
+				u16 value, bool single)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.write)
+		return -EINVAL;
+
+	ret = di->bus.write(di, di->regs[reg_index], value, single);
+	BQ27XXX_REG_ERR(ret, "write", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_index,
+				     u8* data, int len)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.read_bulk)
+		return -EINVAL;
+
+	ret = di->bus.read_bulk(di, di->regs[reg_index], data, len);
+	BQ27XXX_REG_ERR(ret, "read_bulk", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_index,
+				      u8* data, int len)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.write_bulk)
+		return -EINVAL;
+
+	ret = di->bus.write_bulk(di, di->regs[reg_index], data, len);
+	BQ27XXX_REG_ERR(ret, "write_bulk", reg_index);
+
+	return ret;
 }
 
 /*
drivers/power/supply/bq27xxx_battery_i2c.c +81 -1

101 lines not on this page · raw message

include/linux/power/bq27xxx_battery.h +3 -0
index b312bce..c3369fa 100644
@@ -40,6 +40,9 @@ struct bq27xxx_platform_data {
 struct bq27xxx_device_info;
 struct bq27xxx_access_methods {
 	int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single);
+	int (*write)(struct bq27xxx_device_info *di, u8 reg, int value, bool single);
+	int (*read_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
+	int (*write_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
 };
 
 struct bq27xxx_reg_cache {
Signature · 1 line
2.9.3
#2 lore raw

Re: [PATCH v12.1 5/9] power: bq27xxx_battery: Add bulk transfer bus methods

Quoted 19 lines · Liam Breck

From: Matt Ranostay <[email protected]>

Declare bus.write/read_bulk/write_bulk().
Add I2C write/read_bulk/write_bulk() to implement the above.
Add bq27xxx_write/read_block/write_block() helper functions to call the above.

---
 drivers/power/supply/bq27xxx_battery.c     | 54 +++++++++++++++++++-
 drivers/power/supply/bq27xxx_battery_i2c.c | 82 +++++++++++++++++++++++++++++-
 include/linux/power/bq27xxx_battery.h      |  3 ++
 3 files changed, 136 insertions(+), 3 deletions(-)
drivers/power/supply/bq27xxx_battery.c +0 -0
index 398801a..5b6c1b2 100644

drivers/power/supply/bq27xxx_battery.c MODULE_PARM_DESC(poll_interval, · line 796

Quoted 6 lines
drivers/power/supply/bq27xxx_battery.c +3 -0
@@ -791,14 +791,64 @@ MODULE_PARM_DESC(poll_interval,
  * Common code for BQ27xxx devices
  */
 
+#define BQ27XXX_REG_ERR(e,s,i) \
+	if ((e) < 0) dev_dbg(di->dev, "failed to " s " register 0x%02x (index %d)\n", \
+	                     di->regs[i], (i))

Not needed, this is avoided in kernel, here you only save one line total anyway and it is harder to read now. Plus this is not kernel source style correct do have the if and statement on the same line.

Quoted 183 lines
drivers/power/supply/bq27xxx_battery.c +49 -2
+
 static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
 			       bool single)
 {
-	/* Reports EINVAL for invalid/missing registers */
+	int ret;
+
 	if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
 		return -EINVAL;
 
-	return di->bus.read(di, di->regs[reg_index], single);
+	ret = di->bus.read(di, di->regs[reg_index], single);
+	BQ27XXX_REG_ERR(ret, "read", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index,
+				u16 value, bool single)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.write)
+		return -EINVAL;
+
+	ret = di->bus.write(di, di->regs[reg_index], value, single);
+	BQ27XXX_REG_ERR(ret, "write", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_index,
+				     u8* data, int len)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.read_bulk)
+		return -EINVAL;
+
+	ret = di->bus.read_bulk(di, di->regs[reg_index], data, len);
+	BQ27XXX_REG_ERR(ret, "read_bulk", reg_index);
+
+	return ret;
+}
+
+static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_index,
+				      u8* data, int len)
+{
+	int ret;
+
+	if (!di || di->regs[reg_index] == INVALID_REG_ADDR || !di->bus.write_bulk)
+		return -EINVAL;
+
+	ret = di->bus.write_bulk(di, di->regs[reg_index], data, len);
+	BQ27XXX_REG_ERR(ret, "write_bulk", reg_index);
+
+	return ret;
 }
 
 /*
drivers/power/supply/bq27xxx_battery_i2c.c +81 -1
index c68fbc3..a597221 100644
@@ -38,7 +38,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
 {
 	struct i2c_client *client = to_i2c_client(di->dev);
 	struct i2c_msg msg[2];
-	unsigned char data[2];
+	u8 data[2];
 	int ret;
 
 	if (!client->adapter)
@@ -68,6 +68,82 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
 	return ret;
 }
 
+static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg,
+				     int value, bool single)
+{
+	struct i2c_client *client = to_i2c_client(di->dev);
+	struct i2c_msg msg;
+	u8 data[4];
+	int ret;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	data[0] = reg;
+	if (single) {
+		data[1] = (u8) value;
+		msg.len = 2;
+	} else {
+		put_unaligned_le16(value, &data[1]);
+		msg.len = 3;
+	}
+
+	msg.buf = data;
+	msg.addr = client->addr;
+	msg.flags = 0;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EINVAL;
+	return 0;
+}
+
+static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg,
+					 u8 *data, int len)
+{
+	struct i2c_client *client = to_i2c_client(di->dev);
+	int ret;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	ret = i2c_smbus_read_i2c_block_data(client, reg, len, data);
+	if (ret < 0)
+		return ret;
+	if (ret != len)
+		return -EINVAL;
+	return 0;
+}
+
+static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di,
+					  u8 reg, u8 *data, int len)
+{
+	struct i2c_client *client = to_i2c_client(di->dev);
+	struct i2c_msg msg;
+	u8 buf[33];
+	int ret;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	buf[0] = reg;
+	memcpy(&buf[1], data, len);
+
+	msg.buf = buf;
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.len = len + 1;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EINVAL;
+	return 0;
+}
+
 static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
 				     const struct i2c_device_id *id)
 {
@@ -95,7 +171,11 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
 	di->dev = &client->dev;
 	di->chip = id->driver_data;
 	di->name = name;
+
 	di->bus.read = bq27xxx_battery_i2c_read;
+	di->bus.write = bq27xxx_battery_i2c_write;
+	di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read;
+	di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write;
 
 	ret = bq27xxx_battery_setup(di);
 	if (ret)
include/linux/power/bq27xxx_battery.h +3 -0
index b312bce..c3369fa 100644
@@ -40,6 +40,9 @@ struct bq27xxx_platform_data {
 struct bq27xxx_device_info;
 struct bq27xxx_access_methods {
 	int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single);
+	int (*write)(struct bq27xxx_device_info *di, u8 reg, int value, bool single);
+	int (*read_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
+	int (*write_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
 };
 
 struct bq27xxx_reg_cache {
author
#3 lore raw

PATCH 6/9 power: bq27xxx_battery: Add chip data memory read/write support

In reply to Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +350 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC	BIT(0)
 #define BQ27XXX_FLAG_SOCF	BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1	BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP	BIT(4)
 #define BQ27XXX_FLAG_FC		BIT(9)
 #define BQ27XXX_FLAG_OTD	BIT(14)
 #define BQ27XXX_FLAG_OTC	BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC		BIT(5)
 #define BQ27000_FLAG_CHGS	BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED			0x20
+#define BQ27XXX_SET_CFGUPDATE		0x13
+#define BQ27XXX_SOFT_RESET		0x42
+#define BQ27XXX_RESET			0x41
+
 #define BQ27XXX_RS			(20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT		(29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT	(3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
 	BQ27XXX_REG_SOC,	/* State-of-Charge */
 	BQ27XXX_REG_DCAP,	/* Design Capacity */
 	BQ27XXX_REG_AP,		/* Average Power */
+	BQ27XXX_DM_CTRL,	/* Block Data Control */
+	BQ27XXX_DM_CLASS,	/* Data Class */
+	BQ27XXX_DM_BLOCK,	/* Data Block */
+	BQ27XXX_DM_DATA,	/* Block Data */
+	BQ27XXX_DM_CKSUM,	/* Block Data Checksum */
 	BQ27XXX_REG_MAX,	/* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ27010] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ2750X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ2751X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27500] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G4] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27530] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27541] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27545] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27421] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x1c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x18,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ	32
+
+struct bq27xxx_dm_reg {
+	u8 subclass_id;
+	u8 offset;
+	u8 bytes;
+	u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+	u8 class;
+	u8 block;
+	u8 data[BQ27XXX_DM_SZ];
+	bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+	.class = (di)->dm_regs[i].subclass_id, \
+	.block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+				      struct bq27xxx_dm_reg *reg)
+{
+	if (buf->class == reg->subclass_id &&
+	    buf->block == reg->offset / BQ27XXX_DM_SZ)
+		return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+	return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+	BQ27XXX_DM_DESIGN_CAPACITY = 0,
+	BQ27XXX_DM_DESIGN_ENERGY,
+	BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+	[BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
 	struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
 	return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+	if (ret >= 0)
+		return 0;
+
+	dev_err(di->dev, "bus error on seal: %d\n", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+	if (ret < 0)
+		goto out;
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on unseal: %d\n", ret);
+	return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+	u16 sum = 0;
+	int i;
+
+	for (i = 0; i < BQ27XXX_DM_SZ; i++)
+		sum += buf->data[i];
+	sum &= 0xff;
+
+	return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+					 struct bq27xxx_dm_buf *buf)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+	if (ret < 0)
+		goto out;
+
+	if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	buf->full = true;
+	buf->updt = false;
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+	return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+					    struct bq27xxx_dm_buf *buf,
+					    enum bq27xxx_dm_reg_id reg_id,
+					    unsigned int val)
+{
+	struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+	const char* str = bq27xxx_dm_reg_name[reg_id];
+	u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+	if (prev == NULL) {
+		dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+		return;
+	}
+
+	if (reg->bytes != 2) {
+		dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+		return;
+	}
+
+	if (!buf->full)
+		return;
+
+	if (be16_to_cpup(prev) == val) {
+		dev_info(di->dev, "%s has %u\n", str, val);
+		return;
+	}
+
+	dev_info(di->dev, "update %s to %u\n", str, val);
+
+	*prev = cpu_to_be16(val);
+	buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+	const int limit = 100;
+	int ret, try = limit;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+			    flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+			    false);
+	if (ret < 0)
+		goto out;
+
+	do {
+		BQ27XXX_MSLEEP(25);
+		ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+		if (ret < 0)
+			goto out;
+	} while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+	if (!try) {
+		dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);
+		return -EINVAL;
+	}
+
+	if (limit-try > 3)
+		dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+					  struct bq27xxx_dm_buf *buf)
+{
+	bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+	int ret;
+
+	if (!buf->updt)
+		return 0;
+
+	if (cfgup) {
+		ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+			    bq27xxx_battery_checksum_dm_block(buf), true);
+	if (ret < 0)
+		goto out;
+
+	/* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+	 * corruption on the '425 chip (and perhaps others), which can damage
+	 * the chip. See TI bqtool for what not to do:
+	 * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+	 */
+
+	if (cfgup) {
+		BQ27XXX_MSLEEP(1);
+		ret = bq27xxx_battery_set_cfgupdate(di, 0);
+		if (ret < 0)
+			return ret;
+	} else {
+		BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+	}
+
+	buf->updt = false;
+
+	return 0;
+
+out:
+	if (cfgup)
+		bq27xxx_battery_set_cfgupdate(di, 0);
+
+	dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+	return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
 	int id;
 	enum bq27xxx_chip chip;
 	const char *name;
+	struct bq27xxx_dm_reg *dm_regs;
+	u32 unseal_key;
 	struct bq27xxx_access_methods bus;
 	struct bq27xxx_reg_cache cache;
 	int charge_design_full;
Signature · 1 line
2.9.3
#4 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Move

Quoted 357 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC	BIT(0)
 #define BQ27XXX_FLAG_SOCF	BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1	BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP	BIT(4)
 #define BQ27XXX_FLAG_FC		BIT(9)
 #define BQ27XXX_FLAG_OTD	BIT(14)
 #define BQ27XXX_FLAG_OTC	BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC		BIT(5)
 #define BQ27000_FLAG_CHGS	BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED			0x20
+#define BQ27XXX_SET_CFGUPDATE		0x13
+#define BQ27XXX_SOFT_RESET		0x42
+#define BQ27XXX_RESET			0x41
+
 #define BQ27XXX_RS			(20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT		(29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT	(3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
 	BQ27XXX_REG_SOC,	/* State-of-Charge */
 	BQ27XXX_REG_DCAP,	/* Design Capacity */
 	BQ27XXX_REG_AP,		/* Average Power */
+	BQ27XXX_DM_CTRL,	/* Block Data Control */
+	BQ27XXX_DM_CLASS,	/* Data Class */
+	BQ27XXX_DM_BLOCK,	/* Data Block */
+	BQ27XXX_DM_DATA,	/* Block Data */
+	BQ27XXX_DM_CKSUM,	/* Block Data Checksum */
 	BQ27XXX_REG_MAX,	/* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ27010] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ2750X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ2751X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27500] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G4] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27530] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27541] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27545] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27421] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x1c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x18,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ	32
+
+struct bq27xxx_dm_reg {
+	u8 subclass_id;
+	u8 offset;
+	u8 bytes;
+	u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+	u8 class;
+	u8 block;
+	u8 data[BQ27XXX_DM_SZ];
+	bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+	.class = (di)->dm_regs[i].subclass_id, \
+	.block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+				      struct bq27xxx_dm_reg *reg)
+{
+	if (buf->class == reg->subclass_id &&
+	    buf->block == reg->offset / BQ27XXX_DM_SZ)
+		return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+	return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+	BQ27XXX_DM_DESIGN_CAPACITY = 0,
+	BQ27XXX_DM_DESIGN_ENERGY,
+	BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+	[BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
 	struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
 	return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+	if (ret >= 0)
+		return 0;
+
+	dev_err(di->dev, "bus error on seal: %d\n", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+	if (ret < 0)
+		goto out;
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on unseal: %d\n", ret);
+	return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+	u16 sum = 0;
+	int i;
+
+	for (i = 0; i < BQ27XXX_DM_SZ; i++)
+		sum += buf->data[i];
+	sum &= 0xff;
+
+	return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+					 struct bq27xxx_dm_buf *buf)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+	if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+		goto out;
+
+	if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	buf->full = true;
+	buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+	return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+					    struct bq27xxx_dm_buf *buf,
+					    enum bq27xxx_dm_reg_id reg_id,
+					    unsigned int val)
+{
+	struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+	const char* str = bq27xxx_dm_reg_name[reg_id];
+	u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+	if (prev == NULL) {
+		dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+		return;
+	}
+
+	if (reg->bytes != 2) {
+		dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+		return;
+	}
+
+	if (!buf->full)
+		return;
+
+	if (be16_to_cpup(prev) == val) {
+		dev_info(di->dev, "%s has %u\n", str, val);
+		return;
+	}
+
+	dev_info(di->dev, "update %s to %u\n", str, val);
+
+	*prev = cpu_to_be16(val);
+	buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+	const int limit = 100;
+	int ret, try = limit;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+			    flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+			    false);
+	if (ret < 0)
+		goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+	do {
+		BQ27XXX_MSLEEP(25);
+		ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+		if (ret < 0)
+			goto out;
+	} while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+	if (!try) {
+		dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+		return -EINVAL;
+	}
+
+	if (limit-try > 3)
+		dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+					  struct bq27xxx_dm_buf *buf)
+{
+	bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+	int ret;
+
+	if (!buf->updt)
+		return 0;
+
+	if (cfgup) {
+		ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+			    bq27xxx_battery_checksum_dm_block(buf), true);
+	if (ret < 0)
+		goto out;
+
+	/* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+	 * corruption on the '425 chip (and perhaps others), which can damage
+	 * the chip. See TI bqtool for what not to do:
+	 * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+	 */
+
+	if (cfgup) {
+		BQ27XXX_MSLEEP(1);
+		ret = bq27xxx_battery_set_cfgupdate(di, 0);
+		if (ret < 0)
+			return ret;
+	} else {
+		BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+	}
+
+	buf->updt = false;
+
+	return 0;
+
+out:
+	if (cfgup)
+		bq27xxx_battery_set_cfgupdate(di, 0);
+
+	dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+	return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
 	int id;
 	enum bq27xxx_chip chip;
 	const char *name;
+	struct bq27xxx_dm_reg *dm_regs;
+	u32 unseal_key;
 	struct bq27xxx_access_methods bus;
 	struct bq27xxx_reg_cache cache;
 	int charge_design_full;
#5 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 13 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Quoted 1 line

Move

Opps, didn't finish this,

Move
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_update_dm_block()

to the next patch were they are used, it will make this patch a lot less controversial.

Quoted 529 lines
Quoted 358 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC	BIT(0)
 #define BQ27XXX_FLAG_SOCF	BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1	BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP	BIT(4)
 #define BQ27XXX_FLAG_FC		BIT(9)
 #define BQ27XXX_FLAG_OTD	BIT(14)
 #define BQ27XXX_FLAG_OTC	BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC		BIT(5)
 #define BQ27000_FLAG_CHGS	BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED			0x20
+#define BQ27XXX_SET_CFGUPDATE		0x13
+#define BQ27XXX_SOFT_RESET		0x42
+#define BQ27XXX_RESET			0x41
+
 #define BQ27XXX_RS			(20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT		(29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT	(3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
 	BQ27XXX_REG_SOC,	/* State-of-Charge */
 	BQ27XXX_REG_DCAP,	/* Design Capacity */
 	BQ27XXX_REG_AP,		/* Average Power */
+	BQ27XXX_DM_CTRL,	/* Block Data Control */
+	BQ27XXX_DM_CLASS,	/* Data Class */
+	BQ27XXX_DM_BLOCK,	/* Data Block */
+	BQ27XXX_DM_DATA,	/* Block Data */
+	BQ27XXX_DM_CKSUM,	/* Block Data Checksum */
 	BQ27XXX_REG_MAX,	/* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ27010] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x0b,
 		[BQ27XXX_REG_DCAP] = 0x76,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
 	},
 	[BQ2750X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ2751X] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27500] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27510G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = 0x2e,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G1] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G2] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27520G4] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x20,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27530] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27541] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27545] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x2c,
 		[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
 		[BQ27XXX_REG_AP] = 0x24,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 	[BQ27421] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_REG_SOC] = 0x1c,
 		[BQ27XXX_REG_DCAP] = 0x3c,
 		[BQ27XXX_REG_AP] = 0x18,
+		[BQ27XXX_DM_CTRL] = 0x61,
+		[BQ27XXX_DM_CLASS] = 0x3e,
+		[BQ27XXX_DM_BLOCK] = 0x3f,
+		[BQ27XXX_DM_DATA] = 0x40,
+		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ	32
+
+struct bq27xxx_dm_reg {
+	u8 subclass_id;
+	u8 offset;
+	u8 bytes;
+	u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+	u8 class;
+	u8 block;
+	u8 data[BQ27XXX_DM_SZ];
+	bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+	.class = (di)->dm_regs[i].subclass_id, \
+	.block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+				      struct bq27xxx_dm_reg *reg)
+{
+	if (buf->class == reg->subclass_id &&
+	    buf->block == reg->offset / BQ27XXX_DM_SZ)
+		return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+	return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+	BQ27XXX_DM_DESIGN_CAPACITY = 0,
+	BQ27XXX_DM_DESIGN_ENERGY,
+	BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+	[BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
 	struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
 	return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+	if (ret >= 0)
+		return 0;
+
+	dev_err(di->dev, "bus error on seal: %d\n", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+	if (ret < 0)
+		goto out;
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on unseal: %d\n", ret);
+	return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+	u16 sum = 0;
+	int i;
+
+	for (i = 0; i < BQ27XXX_DM_SZ; i++)
+		sum += buf->data[i];
+	sum &= 0xff;
+
+	return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+					 struct bq27xxx_dm_buf *buf)
+{
+	int ret;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+	if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+		goto out;
+
+	if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	buf->full = true;
+	buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+	return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+					    struct bq27xxx_dm_buf *buf,
+					    enum bq27xxx_dm_reg_id reg_id,
+					    unsigned int val)
+{
+	struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+	const char* str = bq27xxx_dm_reg_name[reg_id];
+	u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+	if (prev == NULL) {
+		dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+		return;
+	}
+
+	if (reg->bytes != 2) {
+		dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+		return;
+	}
+
+	if (!buf->full)
+		return;
+
+	if (be16_to_cpup(prev) == val) {
+		dev_info(di->dev, "%s has %u\n", str, val);
+		return;
+	}
+
+	dev_info(di->dev, "update %s to %u\n", str, val);
+
+	*prev = cpu_to_be16(val);
+	buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+	const int limit = 100;
+	int ret, try = limit;
+
+	ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+			    flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+			    false);
+	if (ret < 0)
+		goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+	do {
+		BQ27XXX_MSLEEP(25);
+		ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+		if (ret < 0)
+			goto out;
+	} while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+	if (!try) {
+		dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+		return -EINVAL;
+	}
+
+	if (limit-try > 3)
+		dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+	return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+					  struct bq27xxx_dm_buf *buf)
+{
+	bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+	int ret;
+
+	if (!buf->updt)
+		return 0;
+
+	if (cfgup) {
+		ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+			    bq27xxx_battery_checksum_dm_block(buf), true);
+	if (ret < 0)
+		goto out;
+
+	/* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+	 * corruption on the '425 chip (and perhaps others), which can damage
+	 * the chip. See TI bqtool for what not to do:
+	 * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+	 */
+
+	if (cfgup) {
+		BQ27XXX_MSLEEP(1);
+		ret = bq27xxx_battery_set_cfgupdate(di, 0);
+		if (ret < 0)
+			return ret;
+	} else {
+		BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+	}
+
+	buf->updt = false;
+
+	return 0;
+
+out:
+	if (cfgup)
+		bq27xxx_battery_set_cfgupdate(di, 0);
+
+	dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+	return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
 	int id;
 	enum bq27xxx_chip chip;
 	const char *name;
+	struct bq27xxx_dm_reg *dm_regs;
+	u32 unseal_key;
 	struct bq27xxx_access_methods bus;
 	struct bq27xxx_reg_cache cache;
 	int charge_design_full;
author
#6 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 16 lines · Andrew F. Davis
Quoted 13 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Quoted 1 line

Move

Quoted 8 lines

Opps, didn't finish this,

Move
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_update_dm_block()

to the next patch were they are used, it will make this patch a lot less controversial.

I can move update_dm_block, but it's part of the API called by set_config in the next patch. Reconsider?

set_cfgupdate is a requirement for DM write on parts which support it; it has to stay here.

Quoted 528 lines
Quoted 528 lines
Quoted 357 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC    BIT(0)
 #define BQ27XXX_FLAG_SOCF   BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1   BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP  BIT(4)
 #define BQ27XXX_FLAG_FC             BIT(9)
 #define BQ27XXX_FLAG_OTD    BIT(14)
 #define BQ27XXX_FLAG_OTC    BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC             BIT(5)
 #define BQ27000_FLAG_CHGS   BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                      0x20
+#define BQ27XXX_SET_CFGUPDATE               0x13
+#define BQ27XXX_SOFT_RESET          0x42
+#define BQ27XXX_RESET                       0x41
+
 #define BQ27XXX_RS                  (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT              (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT    (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
     BQ27XXX_REG_SOC,        /* State-of-Charge */
     BQ27XXX_REG_DCAP,       /* Design Capacity */
     BQ27XXX_REG_AP,         /* Average Power */
+    BQ27XXX_DM_CTRL,        /* Block Data Control */
+    BQ27XXX_DM_CLASS,       /* Data Class */
+    BQ27XXX_DM_BLOCK,       /* Data Block */
+    BQ27XXX_DM_DATA,        /* Block Data */
+    BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
     BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ27010] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ2750X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ2751X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27500] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G4] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27530] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27541] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27545] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27421] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x1c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x18,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ       32
+
+struct bq27xxx_dm_reg {
+    u8 subclass_id;
+    u8 offset;
+    u8 bytes;
+    u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+    u8 class;
+    u8 block;
+    u8 data[BQ27XXX_DM_SZ];
+    bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+    .class = (di)->dm_regs[i].subclass_id, \
+    .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                  struct bq27xxx_dm_reg *reg)
+{
+    if (buf->class == reg->subclass_id &&
+        buf->block == reg->offset / BQ27XXX_DM_SZ)
+            return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+    return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+    BQ27XXX_DM_DESIGN_CAPACITY = 0,
+    BQ27XXX_DM_DESIGN_ENERGY,
+    BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+    [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+    [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+    [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
     struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
     return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+    if (ret >= 0)
+            return 0;
+
+    dev_err(di->dev, "bus error on seal: %d\n", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+    if (ret < 0)
+            goto out;
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on unseal: %d\n", ret);
+    return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+    u16 sum = 0;
+    int i;
+
+    for (i = 0; i < BQ27XXX_DM_SZ; i++)
+            sum += buf->data[i];
+    sum &= 0xff;
+
+    return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                     struct bq27xxx_dm_buf *buf)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+    if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+            goto out;
+
+    if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+            ret = -EINVAL;
+            goto out;
+    }
+
+    buf->full = true;
+    buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+    return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                        struct bq27xxx_dm_buf *buf,
+                                        enum bq27xxx_dm_reg_id reg_id,
+                                        unsigned int val)
+{
+    struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+    const char* str = bq27xxx_dm_reg_name[reg_id];
+    u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+    if (prev == NULL) {
+            dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+            return;
+    }
+
+    if (reg->bytes != 2) {
+            dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+            return;
+    }
+
+    if (!buf->full)
+            return;
+
+    if (be16_to_cpup(prev) == val) {
+            dev_info(di->dev, "%s has %u\n", str, val);
+            return;
+    }
+
+    dev_info(di->dev, "update %s to %u\n", str, val);
+
+    *prev = cpu_to_be16(val);
+    buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+    const int limit = 100;
+    int ret, try = limit;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                        flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                        false);
+    if (ret < 0)
+            goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+    do {
+            BQ27XXX_MSLEEP(25);
+            ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+            if (ret < 0)
+                    goto out;
+    } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+    if (!try) {
+            dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+            return -EINVAL;
+    }
+
+    if (limit-try > 3)
+            dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+    bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+    int ret;
+
+    if (!buf->updt)
+            return 0;
+
+    if (cfgup) {
+            ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+            if (ret < 0)
+                    return ret;
+    }
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                        bq27xxx_battery_checksum_dm_block(buf), true);
+    if (ret < 0)
+            goto out;
+
+    /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+     * corruption on the '425 chip (and perhaps others), which can damage
+     * the chip. See TI bqtool for what not to do:
+     * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+     */
+
+    if (cfgup) {
+            BQ27XXX_MSLEEP(1);
+            ret = bq27xxx_battery_set_cfgupdate(di, 0);
+            if (ret < 0)
+                    return ret;
+    } else {
+            BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+    }
+
+    buf->updt = false;
+
+    return 0;
+
+out:
+    if (cfgup)
+            bq27xxx_battery_set_cfgupdate(di, 0);
+
+    dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+    return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
     int id;
     enum bq27xxx_chip chip;
     const char *name;
+    struct bq27xxx_dm_reg *dm_regs;
+    u32 unseal_key;
     struct bq27xxx_access_methods bus;
     struct bq27xxx_reg_cache cache;
     int charge_design_full;
#7 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 23 lines · Liam Breck
Quoted 22 lines · Andrew F. Davis
Quoted 13 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Quoted 1 line

Move

Opps, didn't finish this,

Move
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_update_dm_block()

Quoted 9 lines
Quoted 2 lines

to the next patch were they are used, it will make this patch a lot less controversial.

I can move update_dm_block, but it's part of the API called by set_config in the next patch. Reconsider?

set_cfgupdate is a requirement for DM write on parts which support it; it has to stay here.

Not if you don't add all the "cfgup" stuff in write_dm_block(). I'm not sure I like that stuff anyway, so moving all that to a later patch will allow better review of the changes in this patch. Smaller patches are easier to review.

Quoted 528 lines
Quoted 528 lines
Quoted 528 lines
Quoted 357 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC    BIT(0)
 #define BQ27XXX_FLAG_SOCF   BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1   BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP  BIT(4)
 #define BQ27XXX_FLAG_FC             BIT(9)
 #define BQ27XXX_FLAG_OTD    BIT(14)
 #define BQ27XXX_FLAG_OTC    BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC             BIT(5)
 #define BQ27000_FLAG_CHGS   BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                      0x20
+#define BQ27XXX_SET_CFGUPDATE               0x13
+#define BQ27XXX_SOFT_RESET          0x42
+#define BQ27XXX_RESET                       0x41
+
 #define BQ27XXX_RS                  (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT              (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT    (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
     BQ27XXX_REG_SOC,        /* State-of-Charge */
     BQ27XXX_REG_DCAP,       /* Design Capacity */
     BQ27XXX_REG_AP,         /* Average Power */
+    BQ27XXX_DM_CTRL,        /* Block Data Control */
+    BQ27XXX_DM_CLASS,       /* Data Class */
+    BQ27XXX_DM_BLOCK,       /* Data Block */
+    BQ27XXX_DM_DATA,        /* Block Data */
+    BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
     BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ27010] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ2750X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ2751X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27500] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G4] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27530] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27541] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27545] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27421] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x1c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x18,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ       32
+
+struct bq27xxx_dm_reg {
+    u8 subclass_id;
+    u8 offset;
+    u8 bytes;
+    u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+    u8 class;
+    u8 block;
+    u8 data[BQ27XXX_DM_SZ];
+    bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+    .class = (di)->dm_regs[i].subclass_id, \
+    .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                  struct bq27xxx_dm_reg *reg)
+{
+    if (buf->class == reg->subclass_id &&
+        buf->block == reg->offset / BQ27XXX_DM_SZ)
+            return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+    return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+    BQ27XXX_DM_DESIGN_CAPACITY = 0,
+    BQ27XXX_DM_DESIGN_ENERGY,
+    BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+    [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+    [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+    [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
     struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
     return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+    if (ret >= 0)
+            return 0;
+
+    dev_err(di->dev, "bus error on seal: %d\n", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+    if (ret < 0)
+            goto out;
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on unseal: %d\n", ret);
+    return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+    u16 sum = 0;
+    int i;
+
+    for (i = 0; i < BQ27XXX_DM_SZ; i++)
+            sum += buf->data[i];
+    sum &= 0xff;
+
+    return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                     struct bq27xxx_dm_buf *buf)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+    if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+            goto out;
+
+    if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+            ret = -EINVAL;
+            goto out;
+    }
+
+    buf->full = true;
+    buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+    return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                        struct bq27xxx_dm_buf *buf,
+                                        enum bq27xxx_dm_reg_id reg_id,
+                                        unsigned int val)
+{
+    struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+    const char* str = bq27xxx_dm_reg_name[reg_id];
+    u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+    if (prev == NULL) {
+            dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+            return;
+    }
+
+    if (reg->bytes != 2) {
+            dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+            return;
+    }
+
+    if (!buf->full)
+            return;
+
+    if (be16_to_cpup(prev) == val) {
+            dev_info(di->dev, "%s has %u\n", str, val);
+            return;
+    }
+
+    dev_info(di->dev, "update %s to %u\n", str, val);
+
+    *prev = cpu_to_be16(val);
+    buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+    const int limit = 100;
+    int ret, try = limit;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                        flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                        false);
+    if (ret < 0)
+            goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+    do {
+            BQ27XXX_MSLEEP(25);
+            ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+            if (ret < 0)
+                    goto out;
+    } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+    if (!try) {
+            dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+            return -EINVAL;
+    }
+
+    if (limit-try > 3)
+            dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+    bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+    int ret;
+
+    if (!buf->updt)
+            return 0;
+
+    if (cfgup) {
+            ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+            if (ret < 0)
+                    return ret;
+    }
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                        bq27xxx_battery_checksum_dm_block(buf), true);
+    if (ret < 0)
+            goto out;
+
+    /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+     * corruption on the '425 chip (and perhaps others), which can damage
+     * the chip. See TI bqtool for what not to do:
+     * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+     */
+
+    if (cfgup) {
+            BQ27XXX_MSLEEP(1);
+            ret = bq27xxx_battery_set_cfgupdate(di, 0);
+            if (ret < 0)
+                    return ret;
+    } else {
+            BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+    }
+
+    buf->updt = false;
+
+    return 0;
+
+out:
+    if (cfgup)
+            bq27xxx_battery_set_cfgupdate(di, 0);
+
+    dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+    return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
     int id;
     enum bq27xxx_chip chip;
     const char *name;
+    struct bq27xxx_dm_reg *dm_regs;
+    u32 unseal_key;
     struct bq27xxx_access_methods bus;
     struct bq27xxx_reg_cache cache;
     int charge_design_full;
author
#8 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 32 lines · Andrew F. Davis
Quoted 29 lines · Liam Breck
Quoted 16 lines · Andrew F. Davis
Quoted 13 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Quoted 1 line

Move

Quoted 8 lines

Opps, didn't finish this,

Move
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_update_dm_block()

to the next patch were they are used, it will make this patch a lot less controversial.

I can move update_dm_block, but it's part of the API called by set_config in the next patch. Reconsider?

Quoted 8 lines
Quoted 3 lines

set_cfgupdate is a requirement for DM write on parts which support it; it has to stay here.

Not if you don't add all the "cfgup" stuff in write_dm_block(). I'm not sure I like that stuff anyway, so moving all that to a later patch will allow better review of the changes in this patch. Smaller patches are easier to review.

set_cfgupdate is required by the chips to perform a block write. It really does have to stay.

I will move update_dm_block and the dm_reg code it uses to the following patch.

Quoted 528 lines
Quoted 528 lines
Quoted 528 lines
Quoted 528 lines
Quoted 357 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC    BIT(0)
 #define BQ27XXX_FLAG_SOCF   BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1   BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP  BIT(4)
 #define BQ27XXX_FLAG_FC             BIT(9)
 #define BQ27XXX_FLAG_OTD    BIT(14)
 #define BQ27XXX_FLAG_OTC    BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC             BIT(5)
 #define BQ27000_FLAG_CHGS   BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                      0x20
+#define BQ27XXX_SET_CFGUPDATE               0x13
+#define BQ27XXX_SOFT_RESET          0x42
+#define BQ27XXX_RESET                       0x41
+
 #define BQ27XXX_RS                  (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT              (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT    (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
     BQ27XXX_REG_SOC,        /* State-of-Charge */
     BQ27XXX_REG_DCAP,       /* Design Capacity */
     BQ27XXX_REG_AP,         /* Average Power */
+    BQ27XXX_DM_CTRL,        /* Block Data Control */
+    BQ27XXX_DM_CLASS,       /* Data Class */
+    BQ27XXX_DM_BLOCK,       /* Data Block */
+    BQ27XXX_DM_DATA,        /* Block Data */
+    BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
     BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ27010] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x0b,
             [BQ27XXX_REG_DCAP] = 0x76,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
     },
     [BQ2750X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ2751X] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27500] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27510G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = 0x2e,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G1] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G2] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G3] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27520G4] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x20,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27530] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27541] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27545] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x2c,
             [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
             [BQ27XXX_REG_AP] = 0x24,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
     [BQ27421] = {
             [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
             [BQ27XXX_REG_SOC] = 0x1c,
             [BQ27XXX_REG_DCAP] = 0x3c,
             [BQ27XXX_REG_AP] = 0x18,
+            [BQ27XXX_DM_CTRL] = 0x61,
+            [BQ27XXX_DM_CLASS] = 0x3e,
+            [BQ27XXX_DM_BLOCK] = 0x3f,
+            [BQ27XXX_DM_DATA] = 0x40,
+            [BQ27XXX_DM_CKSUM] = 0x60,
     },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ       32
+
+struct bq27xxx_dm_reg {
+    u8 subclass_id;
+    u8 offset;
+    u8 bytes;
+    u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+    u8 class;
+    u8 block;
+    u8 data[BQ27XXX_DM_SZ];
+    bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+    .class = (di)->dm_regs[i].subclass_id, \
+    .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                  struct bq27xxx_dm_reg *reg)
+{
+    if (buf->class == reg->subclass_id &&
+        buf->block == reg->offset / BQ27XXX_DM_SZ)
+            return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+    return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+    BQ27XXX_DM_DESIGN_CAPACITY = 0,
+    BQ27XXX_DM_DESIGN_ENERGY,
+    BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+    [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+    [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+    [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
     struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
     return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+    if (ret >= 0)
+            return 0;
+
+    dev_err(di->dev, "bus error on seal: %d\n", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+    if (ret < 0)
+            goto out;
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on unseal: %d\n", ret);
+    return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+    u16 sum = 0;
+    int i;
+
+    for (i = 0; i < BQ27XXX_DM_SZ; i++)
+            sum += buf->data[i];
+    sum &= 0xff;
+
+    return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                     struct bq27xxx_dm_buf *buf)
+{
+    int ret;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+    if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+            goto out;
+
+    if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+            ret = -EINVAL;
+            goto out;
+    }
+
+    buf->full = true;
+    buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+    return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                        struct bq27xxx_dm_buf *buf,
+                                        enum bq27xxx_dm_reg_id reg_id,
+                                        unsigned int val)
+{
+    struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+    const char* str = bq27xxx_dm_reg_name[reg_id];
+    u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+    if (prev == NULL) {
+            dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+            return;
+    }
+
+    if (reg->bytes != 2) {
+            dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+            return;
+    }
+
+    if (!buf->full)
+            return;
+
+    if (be16_to_cpup(prev) == val) {
+            dev_info(di->dev, "%s has %u\n", str, val);
+            return;
+    }
+
+    dev_info(di->dev, "update %s to %u\n", str, val);
+
+    *prev = cpu_to_be16(val);
+    buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+    const int limit = 100;
+    int ret, try = limit;
+
+    ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                        flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                        false);
+    if (ret < 0)
+            goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+    do {
+            BQ27XXX_MSLEEP(25);
+            ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+            if (ret < 0)
+                    goto out;
+    } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+    if (!try) {
+            dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+            return -EINVAL;
+    }
+
+    if (limit-try > 3)
+            dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+    return 0;
+
+out:
+    dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+    return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+    bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+    int ret;
+
+    if (!buf->updt)
+            return 0;
+
+    if (cfgup) {
+            ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+            if (ret < 0)
+                    return ret;
+    }
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+    if (ret < 0)
+            goto out;
+
+    BQ27XXX_MSLEEP(1);
+
+    ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+    if (ret < 0)
+            goto out;
+
+    ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                        bq27xxx_battery_checksum_dm_block(buf), true);
+    if (ret < 0)
+            goto out;
+
+    /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+     * corruption on the '425 chip (and perhaps others), which can damage
+     * the chip. See TI bqtool for what not to do:
+     * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+     */
+
+    if (cfgup) {
+            BQ27XXX_MSLEEP(1);
+            ret = bq27xxx_battery_set_cfgupdate(di, 0);
+            if (ret < 0)
+                    return ret;
+    } else {
+            BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+    }
+
+    buf->updt = false;
+
+    return 0;
+
+out:
+    if (cfgup)
+            bq27xxx_battery_set_cfgupdate(di, 0);
+
+    dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+    return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
     int id;
     enum bq27xxx_chip chip;
     const char *name;
+    struct bq27xxx_dm_reg *dm_regs;
+    u32 unseal_key;
     struct bq27xxx_access_methods bus;
     struct bq27xxx_reg_cache cache;
     int charge_design_full;
author
#9 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

In reply to Andrew F. Davis
Quoted 376 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Move

Quoted 361 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +206 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC     BIT(0)
 #define BQ27XXX_FLAG_SOCF    BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1    BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP   BIT(4)
 #define BQ27XXX_FLAG_FC              BIT(9)
 #define BQ27XXX_FLAG_OTD     BIT(14)
 #define BQ27XXX_FLAG_OTC     BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC              BIT(5)
 #define BQ27000_FLAG_CHGS    BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                       0x20
+#define BQ27XXX_SET_CFGUPDATE                0x13
+#define BQ27XXX_SOFT_RESET           0x42
+#define BQ27XXX_RESET                        0x41
+
 #define BQ27XXX_RS                   (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT               (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT     (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
      BQ27XXX_REG_SOC,        /* State-of-Charge */
      BQ27XXX_REG_DCAP,       /* Design Capacity */
      BQ27XXX_REG_AP,         /* Average Power */
+     BQ27XXX_DM_CTRL,        /* Block Data Control */
+     BQ27XXX_DM_CLASS,       /* Data Class */
+     BQ27XXX_DM_BLOCK,       /* Data Block */
+     BQ27XXX_DM_DATA,        /* Block Data */
+     BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
      BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ27010] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ2750X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ2751X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27500] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G4] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27530] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27541] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27545] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27421] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x1c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x18,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ        32
+
+struct bq27xxx_dm_reg {
+     u8 subclass_id;
+     u8 offset;
+     u8 bytes;
+     u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+     u8 class;
+     u8 block;
+     u8 data[BQ27XXX_DM_SZ];
+     bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+     .class = (di)->dm_regs[i].subclass_id, \
+     .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                   struct bq27xxx_dm_reg *reg)
+{
+     if (buf->class == reg->subclass_id &&
+         buf->block == reg->offset / BQ27XXX_DM_SZ)
+             return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+     return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+     BQ27XXX_DM_DESIGN_CAPACITY = 0,
+     BQ27XXX_DM_DESIGN_ENERGY,
+     BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+     [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+     [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+     [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
      struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
      return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+     if (ret >= 0)
+             return 0;
+
+     dev_err(di->dev, "bus error on seal: %d\n", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+     if (ret < 0)
+             goto out;
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on unseal: %d\n", ret);
+     return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+     u16 sum = 0;
+     int i;
+
+     for (i = 0; i < BQ27XXX_DM_SZ; i++)
+             sum += buf->data[i];
+     sum &= 0xff;
+
+     return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+     if (ret < 0)
+             goto out;
+
+     if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+             ret = -EINVAL;
Quoted 8 lines

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 6 lines
drivers/power/supply/bq27xxx_battery.c +6 -0
+             goto out;
+     }
+
+     buf->full = true;
+     buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Only buf->updt becomes buf->dirty?

Quoted 54 lines
Quoted 54 lines
drivers/power/supply/bq27xxx_battery.c +54 -0
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+     return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                         struct bq27xxx_dm_buf *buf,
+                                         enum bq27xxx_dm_reg_id reg_id,
+                                         unsigned int val)
+{
+     struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+     const char* str = bq27xxx_dm_reg_name[reg_id];
+     u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+     if (prev == NULL) {
+             dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+             return;
+     }
+
+     if (reg->bytes != 2) {
+             dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+             return;
+     }
+
+     if (!buf->full)
+             return;
+
+     if (be16_to_cpup(prev) == val) {
+             dev_info(di->dev, "%s has %u\n", str, val);
+             return;
+     }
+
+     dev_info(di->dev, "update %s to %u\n", str, val);
+
+     *prev = cpu_to_be16(val);
+     buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+     const int limit = 100;
+     int ret, try = limit;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                         flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                         false);
+     if (ret < 0)
+             goto out;
+
+     do {
+             BQ27XXX_MSLEEP(25);
+             ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
Quoted 8 lines

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 6 lines
drivers/power/supply/bq27xxx_battery.c +6 -0
+             if (ret < 0)
+                     goto out;
+     } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+     if (!try) {
+             dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Because the flag argument is a mask, and the flag itself is 0 or 1.

Quoted 95 lines
Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+             return -EINVAL;
+     }
+
+     if (limit-try > 3)
+             dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                       struct bq27xxx_dm_buf *buf)
+{
+     bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+     int ret;
+
+     if (!buf->updt)
+             return 0;
+
+     if (cfgup) {
+             ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+             if (ret < 0)
+                     return ret;
+     }
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                         bq27xxx_battery_checksum_dm_block(buf), true);
+     if (ret < 0)
+             goto out;
+
+     /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+      * corruption on the '425 chip (and perhaps others), which can damage
+      * the chip. See TI bqtool for what not to do:
+      * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+      */
+
+     if (cfgup) {
+             BQ27XXX_MSLEEP(1);
+             ret = bq27xxx_battery_set_cfgupdate(di, 0);
+             if (ret < 0)
+                     return ret;
+     } else {
+             BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+     }
+
+     buf->updt = false;
+
+     return 0;
+
+out:
+     if (cfgup)
+             bq27xxx_battery_set_cfgupdate(di, 0);
+
+     dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+     return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
      int id;
      enum bq27xxx_chip chip;
      const char *name;
+     struct bq27xxx_dm_reg *dm_regs;
+     u32 unseal_key;
      struct bq27xxx_access_methods bus;
      struct bq27xxx_reg_cache cache;
      int charge_design_full;
#10 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 386 lines · Liam Breck
Quoted 376 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Move

Quoted 361 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +206 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC     BIT(0)
 #define BQ27XXX_FLAG_SOCF    BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1    BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP   BIT(4)
 #define BQ27XXX_FLAG_FC              BIT(9)
 #define BQ27XXX_FLAG_OTD     BIT(14)
 #define BQ27XXX_FLAG_OTC     BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC              BIT(5)
 #define BQ27000_FLAG_CHGS    BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                       0x20
+#define BQ27XXX_SET_CFGUPDATE                0x13
+#define BQ27XXX_SOFT_RESET           0x42
+#define BQ27XXX_RESET                        0x41
+
 #define BQ27XXX_RS                   (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT               (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT     (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
      BQ27XXX_REG_SOC,        /* State-of-Charge */
      BQ27XXX_REG_DCAP,       /* Design Capacity */
      BQ27XXX_REG_AP,         /* Average Power */
+     BQ27XXX_DM_CTRL,        /* Block Data Control */
+     BQ27XXX_DM_CLASS,       /* Data Class */
+     BQ27XXX_DM_BLOCK,       /* Data Block */
+     BQ27XXX_DM_DATA,        /* Block Data */
+     BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
      BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ27010] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ2750X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ2751X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27500] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G4] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27530] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27541] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27545] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27421] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x1c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x18,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ        32
+
+struct bq27xxx_dm_reg {
+     u8 subclass_id;
+     u8 offset;
+     u8 bytes;
+     u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+     u8 class;
+     u8 block;
+     u8 data[BQ27XXX_DM_SZ];
+     bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+     .class = (di)->dm_regs[i].subclass_id, \
+     .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                   struct bq27xxx_dm_reg *reg)
+{
+     if (buf->class == reg->subclass_id &&
+         buf->block == reg->offset / BQ27XXX_DM_SZ)
+             return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+     return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+     BQ27XXX_DM_DESIGN_CAPACITY = 0,
+     BQ27XXX_DM_DESIGN_ENERGY,
+     BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+     [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+     [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+     [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
      struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
      return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+     if (ret >= 0)
+             return 0;
+
+     dev_err(di->dev, "bus error on seal: %d\n", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+     if (ret < 0)
+             goto out;
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on unseal: %d\n", ret);
+     return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+     u16 sum = 0;
+     int i;
+
+     for (i = 0; i < BQ27XXX_DM_SZ; i++)
+             sum += buf->data[i];
+     sum &= 0xff;
+
+     return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+     if (ret < 0)
+             goto out;
+
+     if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+             ret = -EINVAL;
Quoted 8 lines

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 6 lines
drivers/power/supply/bq27xxx_battery.c +6 -0
+             goto out;
+     }
+
+     buf->full = true;
+     buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 2 lines

Only buf->updt becomes buf->dirty?

You don't need "full" if you set the buffer clean on by default.

Quoted 63 lines
Quoted 54 lines
Quoted 54 lines
drivers/power/supply/bq27xxx_battery.c +54 -0
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+     return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                         struct bq27xxx_dm_buf *buf,
+                                         enum bq27xxx_dm_reg_id reg_id,
+                                         unsigned int val)
+{
+     struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+     const char* str = bq27xxx_dm_reg_name[reg_id];
+     u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+     if (prev == NULL) {
+             dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+             return;
+     }
+
+     if (reg->bytes != 2) {
+             dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+             return;
+     }
+
+     if (!buf->full)
+             return;
+
+     if (be16_to_cpup(prev) == val) {
+             dev_info(di->dev, "%s has %u\n", str, val);
+             return;
+     }
+
+     dev_info(di->dev, "update %s to %u\n", str, val);
+
+     *prev = cpu_to_be16(val);
+     buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+     const int limit = 100;
+     int ret, try = limit;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                         flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                         false);
+     if (ret < 0)
+             goto out;
+
+     do {
+             BQ27XXX_MSLEEP(25);
+             ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
Quoted 8 lines

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 6 lines
drivers/power/supply/bq27xxx_battery.c +6 -0
+             if (ret < 0)
+                     goto out;
+     } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+     if (!try) {
+             dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 2 lines

Because the flag argument is a mask, and the flag itself is 0 or 1.

Then again, why cast to bool

Quoted 95 lines
Quoted 95 lines
Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+             return -EINVAL;
+     }
+
+     if (limit-try > 3)
+             dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                       struct bq27xxx_dm_buf *buf)
+{
+     bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+     int ret;
+
+     if (!buf->updt)
+             return 0;
+
+     if (cfgup) {
+             ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+             if (ret < 0)
+                     return ret;
+     }
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                         bq27xxx_battery_checksum_dm_block(buf), true);
+     if (ret < 0)
+             goto out;
+
+     /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+      * corruption on the '425 chip (and perhaps others), which can damage
+      * the chip. See TI bqtool for what not to do:
+      * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+      */
+
+     if (cfgup) {
+             BQ27XXX_MSLEEP(1);
+             ret = bq27xxx_battery_set_cfgupdate(di, 0);
+             if (ret < 0)
+                     return ret;
+     } else {
+             BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+     }
+
+     buf->updt = false;
+
+     return 0;
+
+out:
+     if (cfgup)
+             bq27xxx_battery_set_cfgupdate(di, 0);
+
+     dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+     return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
      int id;
      enum bq27xxx_chip chip;
      const char *name;
+     struct bq27xxx_dm_reg *dm_regs;
+     u32 unseal_key;
      struct bq27xxx_access_methods bus;
      struct bq27xxx_reg_cache cache;
      int charge_design_full;
author
#11 lore raw

Re: [PATCH v12.1 6/9] power: bq27xxx_battery: Add chip data memory read/write support

Quoted 387 lines · Andrew F. Davis
Quoted 386 lines · Liam Breck
Quoted 384 lines · Andrew F. Davis
Quoted 11 lines · Liam Breck

From: Liam Breck <[email protected]>

Add the following to enable read/write of chip data memory (DM) RAM/NVM/flash:
bq27xxx_battery_seal()
bq27xxx_battery_unseal()
bq27xxx_battery_set_cfgupdate()
bq27xxx_battery_read_dm_block() bq27xxx_battery_update_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block()

Move

Quoted 357 lines
---
 drivers/power/supply/bq27xxx_battery.c | 350 +++++++++++++++++++++++++++++++++
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 352 insertions(+)
drivers/power/supply/bq27xxx_battery.c +202 -0
index 5b6c1b2..3daed9d 100644
@@ -65,6 +65,7 @@
 #define BQ27XXX_FLAG_DSC     BIT(0)
 #define BQ27XXX_FLAG_SOCF    BIT(1) /* State-of-Charge threshold final */
 #define BQ27XXX_FLAG_SOC1    BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP   BIT(4)
 #define BQ27XXX_FLAG_FC              BIT(9)
 #define BQ27XXX_FLAG_OTD     BIT(14)
 #define BQ27XXX_FLAG_OTC     BIT(15)
@@ -78,6 +79,12 @@
 #define BQ27000_FLAG_FC              BIT(5)
 #define BQ27000_FLAG_CHGS    BIT(7) /* Charge state flag */
 
+/* control register params */
+#define BQ27XXX_SEALED                       0x20
+#define BQ27XXX_SET_CFGUPDATE                0x13
+#define BQ27XXX_SOFT_RESET           0x42
+#define BQ27XXX_RESET                        0x41
+
 #define BQ27XXX_RS                   (20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT               (29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT     (3570) /* 3.57 µV * 1000 */
@@ -108,6 +115,11 @@ enum bq27xxx_reg_index {
      BQ27XXX_REG_SOC,        /* State-of-Charge */
      BQ27XXX_REG_DCAP,       /* Design Capacity */
      BQ27XXX_REG_AP,         /* Average Power */
+     BQ27XXX_DM_CTRL,        /* Block Data Control */
+     BQ27XXX_DM_CLASS,       /* Data Class */
+     BQ27XXX_DM_BLOCK,       /* Data Block */
+     BQ27XXX_DM_DATA,        /* Block Data */
+     BQ27XXX_DM_CKSUM,       /* Block Data Checksum */
      BQ27XXX_REG_MAX,        /* sentinel */
 };
 
@@ -131,6 +143,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ27010] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +167,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x0b,
              [BQ27XXX_REG_DCAP] = 0x76,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
      },
      [BQ2750X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +191,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ2751X] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +215,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27500] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +263,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +287,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27510G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +311,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = 0x2e,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G1] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +335,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G2] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +359,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G3] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +383,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27520G4] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +407,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x20,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27530] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +431,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27541] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +455,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27545] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +479,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x2c,
              [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
              [BQ27XXX_REG_AP] = 0x24,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
      [BQ27421] = {
              [BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +503,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
              [BQ27XXX_REG_SOC] = 0x1c,
              [BQ27XXX_REG_DCAP] = 0x3c,
              [BQ27XXX_REG_AP] = 0x18,
+             [BQ27XXX_DM_CTRL] = 0x61,
+             [BQ27XXX_DM_CLASS] = 0x3e,
+             [BQ27XXX_DM_BLOCK] = 0x3f,
+             [BQ27XXX_DM_DATA] = 0x40,
+             [BQ27XXX_DM_CKSUM] = 0x60,
      },
 };
 
@@ -757,6 +849,52 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ        32
+
+struct bq27xxx_dm_reg {
+     u8 subclass_id;
+     u8 offset;
+     u8 bytes;
+     u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+     u8 class;
+     u8 block;
+     u8 data[BQ27XXX_DM_SZ];
+     bool full, updt;
+};
+
+#define BQ27XXX_DM_BUF(di, i) { \
+     .class = (di)->dm_regs[i].subclass_id, \
+     .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
+}
+
+static inline u16* bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
+                                   struct bq27xxx_dm_reg *reg)
+{
+     if (buf->class == reg->subclass_id &&
+         buf->block == reg->offset / BQ27XXX_DM_SZ)
+             return (u16*) (buf->data + reg->offset % BQ27XXX_DM_SZ);
+
+     return NULL;
+}
+
+enum bq27xxx_dm_reg_id {
+     BQ27XXX_DM_DESIGN_CAPACITY = 0,
+     BQ27XXX_DM_DESIGN_ENERGY,
+     BQ27XXX_DM_TERMINATE_VOLTAGE,
+};
+
+static const char* bq27xxx_dm_reg_name[] = {
+     [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
+     [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
+     [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
+};
+
+
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
      struct bq27xxx_device_info *di;
@@ -851,6 +989,218 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in
      return ret;
 }
 
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+     if (ret >= 0)
+             return 0;
+
+     dev_err(di->dev, "bus error on seal: %d\n", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+     if (ret < 0)
+             goto out;
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on unseal: %d\n", ret);
+     return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+     u16 sum = 0;
+     int i;
+
+     for (i = 0; i < BQ27XXX_DM_SZ; i++)
+             sum += buf->data[i];
+     sum &= 0xff;
+
+     return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+                                      struct bq27xxx_dm_buf *buf)
+{
+     int ret;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+     if (ret < 0)

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1065

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+             goto out;
+
+     if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+             ret = -EINVAL;
+             goto out;
+     }
+
+     buf->full = true;
+     buf->updt = false;
+

buf->dirty should take care of all this update tracking.

Quoted 4 lines
Quoted 2 lines

Only buf->updt becomes buf->dirty?

You don't need "full" if you set the buffer clean on by default.

Yes, a dm_buf is empty before a read, not "clean". You can't update an empty dm_buf.

Quoted 63 lines
Quoted 63 lines
Quoted 62 lines
Quoted 50 lines
drivers/power/supply/bq27xxx_battery.c +50 -0
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+     return ret;
+}
+
+static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
+                                         struct bq27xxx_dm_buf *buf,
+                                         enum bq27xxx_dm_reg_id reg_id,
+                                         unsigned int val)
+{
+     struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
+     const char* str = bq27xxx_dm_reg_name[reg_id];
+     u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
+
+     if (prev == NULL) {
+             dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
+             return;
+     }
+
+     if (reg->bytes != 2) {
+             dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
+             return;
+     }
+
+     if (!buf->full)
+             return;
+
+     if (be16_to_cpup(prev) == val) {
+             dev_info(di->dev, "%s has %u\n", str, val);
+             return;
+     }
+
+     dev_info(di->dev, "update %s to %u\n", str, val);
+
+     *prev = cpu_to_be16(val);
+     buf->updt = true;
+}
+
+static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 flag)
+{
+     const int limit = 100;
+     int ret, try = limit;
+
+     ret = bq27xxx_write(di, BQ27XXX_REG_CTRL,
+                         flag ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET,
+                         false);
+     if (ret < 0)
+             goto out;

drivers/power/supply/bq27xxx_battery.c static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in · line 1125

Quoted 10 lines
drivers/power/supply/bq27xxx_battery.c +10 -0
+
+     do {
+             BQ27XXX_MSLEEP(25);
+             ret = di->bus.read(di, di->regs[BQ27XXX_REG_FLAGS], false);
+             if (ret < 0)
+                     goto out;
+     } while ((ret & BQ27XXX_FLAG_CFGUP) != flag && --try);
+
+     if (!try) {
+             dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!flag);

Why cast to bool?

Quoted 4 lines
Quoted 2 lines

Because the flag argument is a mask, and the flag itself is 0 or 1.

Then again, why cast to bool

Because we print the flag state we sought, not the mask to retrieve it.

Quoted 95 lines
Quoted 95 lines
Quoted 95 lines
Quoted 95 lines
drivers/power/supply/bq27xxx_battery.c +78 -0
+             return -EINVAL;
+     }
+
+     if (limit-try > 3)
+             dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!flag, limit-try);
+
+     return 0;
+
+out:
+     dev_err(di->dev, "bus error on %s: %d\n", flag ? "set_cfgupdate" : "soft_reset", ret);
+     return ret;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+                                       struct bq27xxx_dm_buf *buf)
+{
+     bool cfgup = di->chip == BQ27421; /* assume group supports cfgupdate */
+     int ret;
+
+     if (!buf->updt)
+             return 0;
+
+     if (cfgup) {
+             ret = bq27xxx_battery_set_cfgupdate(di, BQ27XXX_FLAG_CFGUP);
+             if (ret < 0)
+                     return ret;
+     }
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+     if (ret < 0)
+             goto out;
+
+     BQ27XXX_MSLEEP(1);
+
+     ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+     if (ret < 0)
+             goto out;
+
+     ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
+                         bq27xxx_battery_checksum_dm_block(buf), true);
+     if (ret < 0)
+             goto out;
+
+     /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
+      * corruption on the '425 chip (and perhaps others), which can damage
+      * the chip. See TI bqtool for what not to do:
+      * http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+      */
+
+     if (cfgup) {
+             BQ27XXX_MSLEEP(1);
+             ret = bq27xxx_battery_set_cfgupdate(di, 0);
+             if (ret < 0)
+                     return ret;
+     } else {
+             BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
+     }
+
+     buf->updt = false;
+
+     return 0;
+
+out:
+     if (cfgup)
+             bq27xxx_battery_set_cfgupdate(di, 0);
+
+     dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+     return ret;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
include/linux/power/bq27xxx_battery.h +2 -0
index c3369fa..227eb08 100644
@@ -64,6 +64,8 @@ struct bq27xxx_device_info {
      int id;
      enum bq27xxx_chip chip;
      const char *name;
+     struct bq27xxx_dm_reg *dm_regs;
+     u32 unseal_key;
      struct bq27xxx_access_methods bus;
      struct bq27xxx_reg_cache cache;
      int charge_design_full;
author
#12 lore raw

PATCH 7/9 power: bq27xxx_battery: Add power_supply_battery_info support

In reply to Liam Breck

From: Liam Breck <[email protected]>

Previously there was no way to configure these chips in the event that the defaults didn't match the battery in question.

We now call power_supply_get_battery_info(), check its values, and write battery data to chip data memory (flash/RAM/NVM).

---
 drivers/power/supply/bq27xxx_battery.c | 105 ++++++++++++++++++++++++++++++++-
 1 file changed, 104 insertions(+), 1 deletion(-)
drivers/power/supply/bq27xxx_battery.c +104 -1
index 3daed9d..0fd96b7 100644
@@ -1201,6 +1201,98 @@ static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
 	return ret;
 }
 
+static void bq27xxx_battery_set_config(struct bq27xxx_device_info *di,
+				       struct power_supply_battery_info *info)
+{
+	struct bq27xxx_dm_buf bd = BQ27XXX_DM_BUF(di, BQ27XXX_DM_DESIGN_CAPACITY);
+	struct bq27xxx_dm_buf bt = BQ27XXX_DM_BUF(di, BQ27XXX_DM_TERMINATE_VOLTAGE);
+
+	if (bq27xxx_battery_unseal(di) < 0)
+		return;
+
+	if (info->charge_full_design_uah != -EINVAL &&
+	    info->energy_full_design_uwh != -EINVAL) {
+		bq27xxx_battery_read_dm_block(di, &bd);
+		/* assume design energy & capacity are in same block */
+		bq27xxx_battery_update_dm_block(di, &bd,
+					BQ27XXX_DM_DESIGN_CAPACITY,
+					info->charge_full_design_uah / 1000);
+		bq27xxx_battery_update_dm_block(di, &bd,
+					BQ27XXX_DM_DESIGN_ENERGY,
+					info->energy_full_design_uwh / 1000);
+	}
+
+	if (info->voltage_min_design_uv != -EINVAL) {
+		bool same = bd.class == bt.class && bd.block == bt.block;
+		if (!same)
+			bq27xxx_battery_read_dm_block(di, &bt);
+		bq27xxx_battery_update_dm_block(di, same ? &bd : &bt,
+					BQ27XXX_DM_TERMINATE_VOLTAGE,
+					info->voltage_min_design_uv / 1000);
+	}
+
+	bq27xxx_battery_write_dm_block(di, &bd);
+	bq27xxx_battery_write_dm_block(di, &bt);
+
+	bq27xxx_battery_seal(di);
+
+	if (di->chip != BQ27421) { /* not a cfgupdate chip, so reset */
+		bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_RESET, false);
+		BQ27XXX_MSLEEP(300); /* reset time is not documented */
+	}
+	/* assume bq27xxx_battery_update() is called hereafter */
+}
+
+void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
+{
+	struct power_supply_battery_info info = {};
+	unsigned int min, max;
+
+	if (!di->dm_regs)
+		return;
+
+	if (power_supply_get_battery_info(di->bat, &info) < 0)
+		return;
+
+	if (info.energy_full_design_uwh != info.charge_full_design_uah) {
+		if (info.energy_full_design_uwh == -EINVAL)
+			dev_warn(di->dev, "missing battery:energy-full-design-microwatt-hours\n");
+		else if (info.charge_full_design_uah == -EINVAL)
+			dev_warn(di->dev, "missing battery:charge-full-design-microamp-hours\n");
+	}
+
+	/* assume min == 0 */
+	max = di->dm_regs[BQ27XXX_DM_DESIGN_ENERGY].max;
+	if (info.energy_full_design_uwh > max * 1000) {
+		dev_err(di->dev, "invalid battery:energy-full-design-microwatt-hours %d\n",
+			info.energy_full_design_uwh);
+		info.energy_full_design_uwh = -EINVAL;
+	}
+
+	/* assume min == 0 */
+	max = di->dm_regs[BQ27XXX_DM_DESIGN_CAPACITY].max;
+	if (info.charge_full_design_uah > max * 1000) {
+		dev_err(di->dev, "invalid battery:charge-full-design-microamp-hours %d\n",
+			info.charge_full_design_uah);
+		info.charge_full_design_uah = -EINVAL;
+	}
+
+	min = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].min;
+	max = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].max;
+	if ((info.voltage_min_design_uv < min * 1000 ||
+	     info.voltage_min_design_uv > max * 1000) &&
+	     info.voltage_min_design_uv != -EINVAL) {
+		dev_err(di->dev, "invalid battery:voltage-min-design-microvolt %d\n",
+			info.voltage_min_design_uv);
+		info.voltage_min_design_uv = -EINVAL;
+	}
+
+	if ((info.energy_full_design_uwh != -EINVAL &&
+	     info.charge_full_design_uah != -EINVAL) ||
+	     info.voltage_min_design_uv  != -EINVAL)
+		bq27xxx_battery_set_config(di, &info);
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
@@ -1718,6 +1810,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
 		ret = bq27xxx_simple_value(di->charge_design_full, val);
 		break;
+	/*
+	 * TODO: Implement these to make registers set from
+	 * 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_CYCLE_COUNT:
 		ret = bq27xxx_simple_value(di->cache.cycle_count, val);
 		break;
@@ -1751,7 +1850,10 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
 int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
 {
 	struct power_supply_desc *psy_desc;
-	struct power_supply_config psy_cfg = { .drv_data = di, };
+	struct power_supply_config psy_cfg = {
+		.of_node = di->dev->of_node,
+		.drv_data = di,
+	};
 
 	INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
 	mutex_init(&di->lock);
@@ -1776,6 +1878,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
 
 	dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION);
 
+	bq27xxx_battery_settings(di);
 	bq27xxx_battery_update(di);
 
 	mutex_lock(&bq27xxx_list_lock);
Signature · 1 line
2.9.3
author
#13 lore raw

PATCH 8/9 power: bq27xxx_battery: Enable chip data memory update for certain chips

In reply to Liam Breck

From: Liam Breck <[email protected]>

Support data memory update of BQ27500, 545, 421, 425, 441, 621.

Create IDs for for previously unID'd chips, to index arrays for unseal keys and data memory register tables.

---
 drivers/power/supply/bq27xxx_battery.c     | 73 +++++++++++++++++++++++++++++-
 drivers/power/supply/bq27xxx_battery_i2c.c | 16 +++----
 include/linux/power/bq27xxx_battery.h      | 11 +++++
 3 files changed, 90 insertions(+), 10 deletions(-)
drivers/power/supply/bq27xxx_battery.c +71 -2
index 0fd96b7..1f82163 100644
@@ -57,7 +57,7 @@
 
 #include <linux/power/bq27xxx_battery.h>
 
-#define DRIVER_VERSION		"1.2.0"
+#define DRIVER_VERSION		"1.3.0"
 
 #define BQ27XXX_MANUFACTURER	"Texas Instruments"
 
@@ -894,6 +894,54 @@ static const char* bq27xxx_dm_reg_name[] = {
 	[BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
 };
 
+static struct bq27xxx_dm_reg bq27500_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY]   = { 48, 10, 2,    0, 65535 },
+	[BQ27XXX_DM_DESIGN_ENERGY]     = { }, /* missing on chip */
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = { 80, 48, 2, 1000, 32767 },
+};
+
+static struct bq27xxx_dm_reg bq27545_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY]   = { 48, 23, 2,    0, 32767 },
+	[BQ27XXX_DM_DESIGN_ENERGY]     = { 48, 25, 2,    0, 32767 },
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = { 80, 67, 2, 2800,  3700 },
+};
+
+static struct bq27xxx_dm_reg bq27421_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY]   = { 82, 10, 2,    0,  8000 },
+	[BQ27XXX_DM_DESIGN_ENERGY]     = { 82, 12, 2,    0, 32767 },
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = { 82, 16, 2, 2500,  3700 },
+};
+
+static struct bq27xxx_dm_reg bq27425_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY]   = { 82, 12, 2,    0, 32767 },
+	[BQ27XXX_DM_DESIGN_ENERGY]     = { 82, 14, 2,    0, 32767 },
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = { 82, 18, 2, 2800,  3700 },
+};
+
+static struct bq27xxx_dm_reg bq27621_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY]   = { 82, 3, 2,    0,  8000 },
+	[BQ27XXX_DM_DESIGN_ENERGY]     = { 82, 5, 2,    0, 32767 },
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] = { 82, 9, 2, 2500,  3700 },
+};
+
+static struct bq27xxx_dm_reg *bq27xxx_dm_regs[] = {
+	[BQ27500] = bq27500_dm_regs,
+	[BQ27545] = bq27545_dm_regs,
+	[BQ27421] = bq27421_dm_regs,
+	[BQ27425] = bq27425_dm_regs,
+	[BQ27441] = bq27421_dm_regs,
+	[BQ27621] = bq27621_dm_regs,
+};
+
+static u32 bq27xxx_unseal_keys[] = {
+	[BQ27500] = 0x04143672,
+	[BQ27545] = 0x04143672,
+	[BQ27421] = 0x80008000,
+	[BQ27425] = 0x04143672,
+	[BQ27441] = 0x80008000,
+	[BQ27621] = 0x80008000,
+};
+
 
 static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
@@ -1855,9 +1903,30 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
 		.drv_data = di,
 	};
 
+	di->unseal_key = bq27xxx_unseal_keys[di->chip];
+	di->dm_regs = bq27xxx_dm_regs[di->chip];
+
+	switch(di->chip) {  /* translate members of categories */
+	case BQ2752X:
+		di->chip = BQ27510G3; break;
+	case BQ27531:
+		di->chip = BQ27530;   break;
+	case BQ27542:
+	case BQ27546:
+	case BQ27742:
+		di->chip = BQ27541;   break;
+	case BQ27425:
+	case BQ27441:
+	case BQ27621:
+		di->chip = BQ27421;   break;
+	default:
+		break;
+	}
+
+	di->regs = bq27xxx_regs[di->chip];
+
 	INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
 	mutex_init(&di->lock);
-	di->regs = bq27xxx_regs[di->chip];
 
 	psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL);
 	if (!psy_desc)
drivers/power/supply/bq27xxx_battery_i2c.c +8 -8
index a597221..0b11ed4 100644
@@ -230,7 +230,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
 	{ "bq27210", BQ27010 },
 	{ "bq27500", BQ2750X },
 	{ "bq27510", BQ2751X },
-	{ "bq27520", BQ2751X },
+	{ "bq27520", BQ2752X },
 	{ "bq27500-1", BQ27500 },
 	{ "bq27510g1", BQ27510G1 },
 	{ "bq27510g2", BQ27510G2 },
@@ -240,16 +240,16 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
 	{ "bq27520g3", BQ27520G3 },
 	{ "bq27520g4", BQ27520G4 },
 	{ "bq27530", BQ27530 },
-	{ "bq27531", BQ27530 },
+	{ "bq27531", BQ27531 },
 	{ "bq27541", BQ27541 },
-	{ "bq27542", BQ27541 },
-	{ "bq27546", BQ27541 },
-	{ "bq27742", BQ27541 },
+	{ "bq27542", BQ27542 },
+	{ "bq27546", BQ27546 },
+	{ "bq27742", BQ27742 },
 	{ "bq27545", BQ27545 },
 	{ "bq27421", BQ27421 },
-	{ "bq27425", BQ27421 },
-	{ "bq27441", BQ27421 },
-	{ "bq27621", BQ27421 },
+	{ "bq27425", BQ27425 },
+	{ "bq27441", BQ27441 },
+	{ "bq27621", BQ27621 },
 	{},
 };
 MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table);
include/linux/power/bq27xxx_battery.h +11 -0
index 227eb08..014e59f 100644
@@ -2,6 +2,7 @@
 #define __LINUX_BQ27X00_BATTERY_H__
 
 enum bq27xxx_chip {
+	/* categories; index for bq27xxx_regs[] */
 	BQ27000 = 1, /* bq27000, bq27200 */
 	BQ27010, /* bq27010, bq27210 */
 	BQ2750X, /* bq27500 deprecated alias */
@@ -18,6 +19,16 @@ enum bq27xxx_chip {
 	BQ27541, /* bq27541, bq27542, bq27546, bq27742 */
 	BQ27545, /* bq27545 */
 	BQ27421, /* bq27421, bq27425, bq27441, bq27621 */
+
+	/* members of categories; translate these to category in _setup() */
+	BQ2752X, /* deprecated alias */
+	BQ27531,
+	BQ27542,
+	BQ27546,
+	BQ27742,
+	BQ27425,
+	BQ27441,
+	BQ27621,
 };
 
 /**
Signature · 1 line
2.9.3
author
#14 lore raw

PATCH 9/9 power: bq27xxx_battery: Remove duplicate register arrays

In reply to Liam Breck

From: Liam Breck <[email protected]>

BQ2751X & BQ27510G3 are identical. BQ27500 & BQ27510G1 & BQ27510G2 are identical. Make BQ2751X & BQ27510G1/2 category members. Remove the duplicate arrays, etc.

---
 drivers/power/supply/bq27xxx_battery.c | 142 +--------------------------------
 include/linux/power/bq27xxx_battery.h  |  12 +--
 2 files changed, 10 insertions(+), 144 deletions(-)
drivers/power/supply/bq27xxx_battery.c +4 -138
index 1f82163..aaa812e 100644
@@ -197,30 +197,6 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_DM_DATA] = 0x40,
 		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
-	[BQ2751X] = {
-		[BQ27XXX_REG_CTRL] = 0x00,
-		[BQ27XXX_REG_TEMP] = 0x06,
-		[BQ27XXX_REG_INT_TEMP] = 0x28,
-		[BQ27XXX_REG_VOLT] = 0x08,
-		[BQ27XXX_REG_AI] = 0x14,
-		[BQ27XXX_REG_FLAGS] = 0x0a,
-		[BQ27XXX_REG_TTE] = 0x16,
-		[BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
-		[BQ27XXX_REG_TTES] = 0x1a,
-		[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
-		[BQ27XXX_REG_NAC] = 0x0c,
-		[BQ27XXX_REG_FCC] = 0x12,
-		[BQ27XXX_REG_CYCT] = 0x1e,
-		[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
-		[BQ27XXX_REG_SOC] = 0x20,
-		[BQ27XXX_REG_DCAP] = 0x2e,
-		[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
-		[BQ27XXX_DM_CTRL] = 0x61,
-		[BQ27XXX_DM_CLASS] = 0x3e,
-		[BQ27XXX_DM_BLOCK] = 0x3f,
-		[BQ27XXX_DM_DATA] = 0x40,
-		[BQ27XXX_DM_CKSUM] = 0x60,
-	},
 	[BQ27500] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
 		[BQ27XXX_REG_TEMP] = 0x06,
@@ -245,54 +221,6 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
 		[BQ27XXX_DM_DATA] = 0x40,
 		[BQ27XXX_DM_CKSUM] = 0x60,
 	},
-	[BQ27510G1] = {
-		[BQ27XXX_REG_CTRL] = 0x00,
-		[BQ27XXX_REG_TEMP] = 0x06,
-		[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
-		[BQ27XXX_REG_VOLT] = 0x08,
-		[BQ27XXX_REG_AI] = 0x14,
-		[BQ27XXX_REG_FLAGS] = 0x0a,
-		[BQ27XXX_REG_TTE] = 0x16,
-		[BQ27XXX_REG_TTF] = 0x18,
-		[BQ27XXX_REG_TTES] = 0x1c,
-		[BQ27XXX_REG_TTECP] = 0x26,
-		[BQ27XXX_REG_NAC] = 0x0c,
-		[BQ27XXX_REG_FCC] = 0x12,
-		[BQ27XXX_REG_CYCT] = 0x2a,
-		[BQ27XXX_REG_AE] = 0x22,
-		[BQ27XXX_REG_SOC] = 0x2c,
-		[BQ27XXX_REG_DCAP] = 0x3c,
-		[BQ27XXX_REG_AP] = 0x24,
-		[BQ27XXX_DM_CTRL] = 0x61,
-		[BQ27XXX_DM_CLASS] = 0x3e,
-		[BQ27XXX_DM_BLOCK] = 0x3f,
-		[BQ27XXX_DM_DATA] = 0x40,
-		[BQ27XXX_DM_CKSUM] = 0x60,
-	},
-	[BQ27510G2] = {
-		[BQ27XXX_REG_CTRL] = 0x00,
-		[BQ27XXX_REG_TEMP] = 0x06,
-		[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
-		[BQ27XXX_REG_VOLT] = 0x08,
-		[BQ27XXX_REG_AI] = 0x14,
-		[BQ27XXX_REG_FLAGS] = 0x0a,
-		[BQ27XXX_REG_TTE] = 0x16,
-		[BQ27XXX_REG_TTF] = 0x18,
-		[BQ27XXX_REG_TTES] = 0x1c,
-		[BQ27XXX_REG_TTECP] = 0x26,
-		[BQ27XXX_REG_NAC] = 0x0c,
-		[BQ27XXX_REG_FCC] = 0x12,
-		[BQ27XXX_REG_CYCT] = 0x2a,
-		[BQ27XXX_REG_AE] = 0x22,
-		[BQ27XXX_REG_SOC] = 0x2c,
-		[BQ27XXX_REG_DCAP] = 0x3c,
-		[BQ27XXX_REG_AP] = 0x24,
-		[BQ27XXX_DM_CTRL] = 0x61,
-		[BQ27XXX_DM_CLASS] = 0x3e,
-		[BQ27XXX_DM_BLOCK] = 0x3f,
-		[BQ27XXX_DM_DATA] = 0x40,
-		[BQ27XXX_DM_CKSUM] = 0x60,
-	},
 	[BQ27510G3] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
 		[BQ27XXX_REG_TEMP] = 0x06,
@@ -571,24 +499,6 @@ static enum power_supply_property bq2750x_battery_props[] = {
 	POWER_SUPPLY_PROP_MANUFACTURER,
 };
 
-static enum power_supply_property bq2751x_battery_props[] = {
-	POWER_SUPPLY_PROP_STATUS,
-	POWER_SUPPLY_PROP_PRESENT,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_CURRENT_NOW,
-	POWER_SUPPLY_PROP_CAPACITY,
-	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
-	POWER_SUPPLY_PROP_TEMP,
-	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
-	POWER_SUPPLY_PROP_TECHNOLOGY,
-	POWER_SUPPLY_PROP_CHARGE_FULL,
-	POWER_SUPPLY_PROP_CHARGE_NOW,
-	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
-	POWER_SUPPLY_PROP_CYCLE_COUNT,
-	POWER_SUPPLY_PROP_HEALTH,
-	POWER_SUPPLY_PROP_MANUFACTURER,
-};
-
 static enum power_supply_property bq27500_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_PRESENT,
@@ -610,48 +520,6 @@ static enum power_supply_property bq27500_battery_props[] = {
 	POWER_SUPPLY_PROP_MANUFACTURER,
 };
 
-static enum power_supply_property bq27510g1_battery_props[] = {
-	POWER_SUPPLY_PROP_STATUS,
-	POWER_SUPPLY_PROP_PRESENT,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_CURRENT_NOW,
-	POWER_SUPPLY_PROP_CAPACITY,
-	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
-	POWER_SUPPLY_PROP_TEMP,
-	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
-	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
-	POWER_SUPPLY_PROP_TECHNOLOGY,
-	POWER_SUPPLY_PROP_CHARGE_FULL,
-	POWER_SUPPLY_PROP_CHARGE_NOW,
-	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
-	POWER_SUPPLY_PROP_CYCLE_COUNT,
-	POWER_SUPPLY_PROP_ENERGY_NOW,
-	POWER_SUPPLY_PROP_POWER_AVG,
-	POWER_SUPPLY_PROP_HEALTH,
-	POWER_SUPPLY_PROP_MANUFACTURER,
-};
-
-static enum power_supply_property bq27510g2_battery_props[] = {
-	POWER_SUPPLY_PROP_STATUS,
-	POWER_SUPPLY_PROP_PRESENT,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_CURRENT_NOW,
-	POWER_SUPPLY_PROP_CAPACITY,
-	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
-	POWER_SUPPLY_PROP_TEMP,
-	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
-	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
-	POWER_SUPPLY_PROP_TECHNOLOGY,
-	POWER_SUPPLY_PROP_CHARGE_FULL,
-	POWER_SUPPLY_PROP_CHARGE_NOW,
-	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
-	POWER_SUPPLY_PROP_CYCLE_COUNT,
-	POWER_SUPPLY_PROP_ENERGY_NOW,
-	POWER_SUPPLY_PROP_POWER_AVG,
-	POWER_SUPPLY_PROP_HEALTH,
-	POWER_SUPPLY_PROP_MANUFACTURER,
-};
-
 static enum power_supply_property bq27510g3_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_PRESENT,
@@ -831,10 +699,7 @@ static struct {
 	BQ27XXX_PROP(BQ27000, bq27000_battery_props),
 	BQ27XXX_PROP(BQ27010, bq27010_battery_props),
 	BQ27XXX_PROP(BQ2750X, bq2750x_battery_props),
-	BQ27XXX_PROP(BQ2751X, bq2751x_battery_props),
 	BQ27XXX_PROP(BQ27500, bq27500_battery_props),
-	BQ27XXX_PROP(BQ27510G1, bq27510g1_battery_props),
-	BQ27XXX_PROP(BQ27510G2, bq27510g2_battery_props),
 	BQ27XXX_PROP(BQ27510G3, bq27510g3_battery_props),
 	BQ27XXX_PROP(BQ27520G1, bq27520g1_battery_props),
 	BQ27XXX_PROP(BQ27520G2, bq27520g2_battery_props),
@@ -1541,10 +1406,7 @@ static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags)
 {
 	switch (di->chip) {
 	case BQ2750X:
-	case BQ2751X:
 	case BQ27500:
-	case BQ27510G1:
-	case BQ27510G2:
 	case BQ27510G3:
 	case BQ27520G1:
 	case BQ27520G2:
@@ -1907,8 +1769,12 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
 	di->dm_regs = bq27xxx_dm_regs[di->chip];
 
 	switch(di->chip) {  /* translate members of categories */
+	case BQ2751X:
 	case BQ2752X:
 		di->chip = BQ27510G3; break;
+	case BQ27510G1:
+	case BQ27510G2:
+		di->chip = BQ27500;   break;
 	case BQ27531:
 		di->chip = BQ27530;   break;
 	case BQ27542:
include/linux/power/bq27xxx_battery.h +6 -6
index 014e59f..81f7ffa 100644
@@ -6,11 +6,8 @@ enum bq27xxx_chip {
 	BQ27000 = 1, /* bq27000, bq27200 */
 	BQ27010, /* bq27010, bq27210 */
 	BQ2750X, /* bq27500 deprecated alias */
-	BQ2751X, /* bq27510, bq27520 deprecated alias */
-	BQ27500, /* bq27500/1 */
-	BQ27510G1, /* bq27510G1 */
-	BQ27510G2, /* bq27510G2 */
-	BQ27510G3, /* bq27510G3 */
+	BQ27500, /* bq27500/1, bq27510G1, bq27510G2 */
+	BQ27510G3, /* bq27510G3, bq27510, bq27520 */
 	BQ27520G1, /* bq27520G1 */
 	BQ27520G2, /* bq27520G2 */
 	BQ27520G3, /* bq27520G3 */
@@ -21,7 +18,10 @@ enum bq27xxx_chip {
 	BQ27421, /* bq27421, bq27425, bq27441, bq27621 */
 
 	/* members of categories; translate these to category in _setup() */
-	BQ2752X, /* deprecated alias */
+	BQ2751X, /* bq27510 deprecated alias */
+	BQ2752X, /* bq27520 deprecated alias */
+	BQ27510G1,
+	BQ27510G2,
 	BQ27531,
 	BQ27542,
 	BQ27546,
Signature · 1 line
2.9.3