From mboxrd@z Thu Jan 1 00:00:00 1970 From: "Andrew F. Davis" Subject: Re: [PATCH v12.6 05/11] power: bq27xxx_battery: Add chip data memory read/write support Date: Mon, 10 Apr 2017 09:47:05 -0500 Message-ID: <31030966-fe81-e455-6568-84b05c0bbf44@ti.com> References: <20170410050712.930-1-liam@networkimprov.net> <20170410050712.930-2-liam@networkimprov.net> Mime-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Return-path: Received: from lelnx193.ext.ti.com ([198.47.27.77]:9451 "EHLO lelnx193.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752912AbdDJOrJ (ORCPT ); Mon, 10 Apr 2017 10:47:09 -0400 In-Reply-To: <20170410050712.930-2-liam@networkimprov.net> Sender: linux-pm-owner@vger.kernel.org List-Id: linux-pm@vger.kernel.org To: Liam Breck , linux-pm@vger.kernel.org Cc: Matt Ranostay , Liam Breck On 04/10/2017 12:07 AM, Liam Breck wrote: > From: Liam Breck > > 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_write_dm_block() > bq27xxx_battery_checksum_dm_block() > > Signed-off-by: Matt Ranostay > Signed-off-by: Liam Breck > --- > drivers/power/supply/bq27xxx_battery.c | 251 +++++++++++++++++++++++++++++++++ > include/linux/power/bq27xxx_battery.h | 1 + > 2 files changed, 252 insertions(+) > > diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c > index ede7fac..6fb9bc9 100644 > --- a/drivers/power/supply/bq27xxx_battery.c > +++ b/drivers/power/supply/bq27xxx_battery.c > @@ -5,6 +5,7 @@ > * Copyright (C) 2008 Eurotech S.p.A. > * Copyright (C) 2010-2011 Lars-Peter Clausen > * Copyright (C) 2011 Pali Rohár > + * Copyright (C) 2017 Liam Breck > * > * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. > * > @@ -65,6 +66,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 +80,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,9 +116,21 @@ 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 */ > }; > > +#define BQ27XXX_DM_REG_ROWS \ > + [BQ27XXX_DM_CTRL] = 0x61, \ > + [BQ27XXX_DM_CLASS] = 0x3e, \ > + [BQ27XXX_DM_BLOCK] = 0x3f, \ > + [BQ27XXX_DM_DATA] = 0x40, \ > + [BQ27XXX_DM_CKSUM] = 0x60 > + > /* Register mappings */ > static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27000] = { > @@ -131,6 +151,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 +175,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 +199,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = INVALID_REG_ADDR, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ2751X] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -188,6 +219,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x20, > [BQ27XXX_REG_DCAP] = 0x2e, > [BQ27XXX_REG_AP] = INVALID_REG_ADDR, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27500] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -207,6 +239,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27510G1] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -226,6 +259,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27510G2] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -245,6 +279,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27510G3] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -264,6 +299,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x20, > [BQ27XXX_REG_DCAP] = 0x2e, > [BQ27XXX_REG_AP] = INVALID_REG_ADDR, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27520G1] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -283,6 +319,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27520G2] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -302,6 +339,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27520G3] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -321,6 +359,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27520G4] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -340,6 +379,7 @@ 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_REG_ROWS, > }, > [BQ27530] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -359,6 +399,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27541] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -378,6 +419,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27545] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -397,6 +439,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x2c, > [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, > [BQ27XXX_REG_AP] = 0x24, > + BQ27XXX_DM_REG_ROWS, > }, > [BQ27421] = { > [BQ27XXX_REG_CTRL] = 0x00, > @@ -416,6 +459,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { > [BQ27XXX_REG_SOC] = 0x1c, > [BQ27XXX_REG_DCAP] = 0x3c, > [BQ27XXX_REG_AP] = 0x18, > + BQ27XXX_DM_REG_ROWS, > }, > }; > > @@ -757,6 +801,28 @@ 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_buf - chip data memory buffer > + * @class: data memory subclass_id > + * @block: data memory block number > + * @data: data from/for the block > + * @has_data: true if data has been filled by read > + * @dirty: true if data has changed since last read/write > + * > + * Encapsulates info required to manage chip data memory blocks. > + */ > +struct bq27xxx_dm_buf { > + u8 class; > + u8 block; > + u8 data[BQ27XXX_DM_SZ]; > + bool has_data, dirty; > +}; > + > + > static int poll_interval_param_set(const char *val, const struct kernel_param *kp) > { > struct bq27xxx_device_info *di; > @@ -864,6 +930,191 @@ 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) > + dev_err(di->dev, "bus error on seal: %d\n", ret); > + > + return (ret < 0) ? ret : 0; if (ret < 0) { dev_err(di->dev, "bus error on seal: %d\n", ret); return ret; } return 0; > +} > + > +static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di) > +{ > + int ret; > + > + if (di->unseal_key == 0) { > + dev_err(di->dev, "unseal failed due to missing key\n"); > + return -EINVAL; > + } > + > + 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; > + > + buf->has_data = false; > + > + 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->has_data = true; > + buf->dirty = false; > + > + return 0; > + > +out: > + dev_err(di->dev, "bus error reading chip memory: %d\n", ret); > + return ret; > +} > + > +static int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di, u16 state) > +{ > + const int limit = 100; > + int ret, try = limit; > + > + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, > + state ? 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) != state && --try); > + > + if (!try) { > + dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", !!state); > + return -EINVAL; > + } > + > + if (limit-try > 3) > + dev_warn(di->dev, "cfgupdate %d, retries %d\n", !!state, limit-try); > + > + return 0; > + > +out: > + dev_err(di->dev, "bus error on %s: %d\n", state ? "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->dirty) > + 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->dirty = 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. > diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h > index c3369fa..b1defb8 100644 > --- a/include/linux/power/bq27xxx_battery.h > +++ b/include/linux/power/bq27xxx_battery.h > @@ -64,6 +64,7 @@ struct bq27xxx_device_info { > int id; > enum bq27xxx_chip chip; > const char *name; > + u32 unseal_key; > struct bq27xxx_access_methods bus; > struct bq27xxx_reg_cache cache; > int charge_design_full; >