diff mbox

[v9.3,7/8] power: bq27xxx_battery: Add power_supply_battery_info support

Message ID 20170310002726.30949-2-liam@networkimprov.net (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Liam Breck March 10, 2017, 12:27 a.m. UTC
From: Liam Breck <kernel@networkimprov.net>

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

BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
and writes battery data to chip RAM or non-volatile memory.

Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.

Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
Signed-off-by: Liam Breck <kernel@networkimprov.net>
---
 drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
 include/linux/power/bq27xxx_battery.h  |   2 +
 2 files changed, 468 insertions(+), 10 deletions(-)

Comments

Liam Breck March 10, 2017, 9:01 p.m. UTC | #1
Andrew, any comments on these last two drafts?


On Thu, Mar 9, 2017 at 4:27 PM, Liam Breck <liam@networkimprov.net> wrote:
> From: Liam Breck <kernel@networkimprov.net>
>
> Previously there was no way to configure chip registers in the event that the
> defaults didn't match the battery in question.
>
> BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
> and writes battery data to chip RAM or non-volatile memory.
>
> Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.
>
> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
> Signed-off-by: Liam Breck <kernel@networkimprov.net>
> ---
>  drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
>  include/linux/power/bq27xxx_battery.h  |   2 +
>  2 files changed, 468 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
> index 8ce2940..f382d18 100644
> --- a/drivers/power/supply/bq27xxx_battery.c
> +++ b/drivers/power/supply/bq27xxx_battery.c
> @@ -51,7 +51,7 @@
>
>  #include <linux/power/bq27xxx_battery.h>
>
> -#define DRIVER_VERSION         "1.2.0"
> +#define DRIVER_VERSION         "1.3.0"
>
>  #define BQ27XXX_MANUFACTURER   "Texas Instruments"
>
> @@ -59,6 +59,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)
> @@ -72,6 +73,11 @@
>  #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_RS                     (20) /* Resistor sense mOhm */
>  #define BQ27XXX_POWER_CONSTANT         (29200) /* 29.2 µV^2 * 1000 */
>  #define BQ27XXX_CURRENT_CONSTANT       (3570) /* 3.57 µV * 1000 */
> @@ -102,6 +108,11 @@ enum bq27xxx_reg_index {
>         BQ27XXX_REG_SOC,        /* State-of-Charge */
>         BQ27XXX_REG_DCAP,       /* Design Capacity */
>         BQ27XXX_REG_AP,         /* Average Power */
> +       BQ27XXX_DM_CTRL,        /* BlockDataControl() */
> +       BQ27XXX_DM_CLASS,       /* DataClass() */
> +       BQ27XXX_DM_BLOCK,       /* DataBlock() */
> +       BQ27XXX_DM_DATA,        /* BlockData() */
> +       BQ27XXX_DM_CKSUM,       /* BlockDataChecksum() */
>         BQ27XXX_REG_MAX,        /* sentinel */
>  };
>
> @@ -125,6 +136,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,
> @@ -144,6 +160,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,
>         },
>         [BQ27500] = {
>                 [BQ27XXX_REG_CTRL] = 0x00,
> @@ -163,6 +184,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,
>         },
>         [BQ27510] = {
>                 [BQ27XXX_REG_CTRL] = 0x00,
> @@ -182,6 +208,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,
>         },
>         [BQ27530] = {
>                 [BQ27XXX_REG_CTRL] = 0x00,
> @@ -201,6 +232,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,
> @@ -220,6 +256,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,
> @@ -239,6 +280,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,
> @@ -258,6 +304,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,
>         },
>  };
>
> @@ -432,6 +483,82 @@ static struct {
>  static DEFINE_MUTEX(bq27xxx_list_lock);
>  static LIST_HEAD(bq27xxx_battery_devices);
>
> +#define BQ27XXX_DM_SZ  32
> +
> +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
> +
> +struct bq27xxx_dm_reg {
> +       u8 subclass_id;
> +       u8 offset;
> +       u8 bytes;
> +       u16 min, max;
> +};
> +
> +struct bq27xxx_dm_buf {
> +       u8 class;
> +       u8 block;
> +       u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
>  {
>         struct bq27xxx_device_info *di;
> @@ -476,6 +603,316 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
>         return di->bus.read(di, di->regs[reg_index], single);
>  }
>
> +static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
> +                                         bool state)
> +{
> +       int ret;
> +
> +       if (state) {
> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
> +               if (ret < 0)
> +                       goto out;
> +       } else {
> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
> +               if (ret < 0)
> +                       goto out;
> +
> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
> +               if (ret < 0)
> +                       goto out;
> +       }
> +       return 0;
> +
> +out:
> +       dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
> +       if (ret < 0)
> +               goto out;
> +
> +       BQ27XXX_MSLEEP(1);
> +
> +       ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = di->bus.read(di, di->regs[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,
> +                                        bool state)
> +{
> +       int ret, try=100;
> +
> +       ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */
> +               dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
> +
> +       if (try)
> +               return 0;
> +
> +       dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
> +       return -EINVAL;
> +
> +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->updt)
> +               return 0;
> +
> +       if (cfgup) {
> +               ret = bq27xxx_battery_set_cfgupdate(di, true);
> +               if (ret < 0)
> +                       return ret;
> +       }
> +
> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
> +       if (ret < 0)
> +               goto out;
> +
> +       BQ27XXX_MSLEEP(1);
> +
> +       ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
> +                           bq27xxx_battery_checksum_dm_block(buf), true);
> +       if (ret < 0)
> +               goto out;
> +
> +       /* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
> +        * If the 'time' delay is insufficient, NVM corruption results on
> +        * the '425 chip (and perhaps others), which could damage the chip.
> +        * It was suggested in this TI tool:
> +        *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
> +        *
> +        * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
> +        * 2. write(BQ27XXX_DM_BLOCK, buf->block)
> +        * 3. sum = read(BQ27XXX_DM_CKSUM)
> +        * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
> +        *      report error
> +        */
> +
> +       if (cfgup) {
> +               BQ27XXX_MSLEEP(1);
> +               ret = bq27xxx_battery_set_cfgupdate(di, false);
> +               if (ret < 0)
> +                       return ret;
> +       } else {
> +               /* flash DM updates in <100ms, but reset time isn't documented */
> +               BQ27XXX_MSLEEP(400);
> +       }
> +
> +       buf->updt = false;
> +       return 0;
> +
> +out:
> +       if (cfgup)
> +               bq27xxx_battery_set_cfgupdate(di, false);
> +
> +       dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
> +       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 (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);
> +}
> +
> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
> +{
> +       struct power_supply_battery_info info = {};
> +       unsigned int min, max;
> +
> +       /* functions don't exist for writing data so abort */
> +       if (!di->bus.write || !di->bus.write_bulk)
> +               return;
> +
> +       /* no settings to be set for this chipset so abort */
> +       if (!di->dm_regs)
> +               return;
> +
> +       if (bq27xxx_battery_set_seal_state(di, false) < 0)
> +               return;
> +
> +       if (power_supply_get_battery_info(di->bat, &info) < 0)
> +               goto out;
> +
> +       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)
> +               goto out;
> +
> +       bq27xxx_battery_set_config(di, &info);
> +
> +out:
> +       bq27xxx_battery_set_seal_state(di, true);
> +}
> +
>  /*
>   * Return the battery State-of-Charge
>   * Or < 0 if something fails.
> @@ -985,6 +1422,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;
> @@ -1015,20 +1459,31 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
>         schedule_delayed_work(&di->work, 0);
>  }
>
> +#define BQ27XXX_INIT(c,d,k)   \
> +       di->chip       = (c); \
> +       di->dm_regs    = (d); \
> +       di->unseal_key = (k)
> +
>  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,
> +       };
>
>         switch(di->chip) {
> -       case BQ27520: di->chip = BQ27510; break;
> -       case BQ27531: di->chip = BQ27530; break;
> -       case BQ27542: di->chip = BQ27541; break;
> -       case BQ27546: di->chip = BQ27541; break;
> -       case BQ27742: di->chip = BQ27541; break;
> -       case BQ27425: di->chip = BQ27421; break;
> -       case BQ27441: di->chip = BQ27421; break;
> -       case BQ27621: di->chip = BQ27421; break;
> +       case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
> +       case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
> +       case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
> +       case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
> +       case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
> +       case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
> +       case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
> +       case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
> +       case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
> +       case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
> +       case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
>         default: break;
>         }
>
> @@ -1055,6 +1510,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);
> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
> index 90b7ca1..9a1c30b 100644
> --- a/include/linux/power/bq27xxx_battery.h
> +++ b/include/linux/power/bq27xxx_battery.h
> @@ -66,6 +66,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;
> --
> 2.9.3
>
Andrew Davis March 10, 2017, 9:25 p.m. UTC | #2
On 03/10/2017 03:01 PM, Liam Breck wrote:
> Andrew, any comments on these last two drafts?
> 

1) Don't ping maintainers/reviewers, I haven't forgotten about you, it
hasn't been even 24 hours since you posted this.
2) I have lots of comments, but it's Friday and I don't feel like
reviewing stuff, if you want some comments to work on go read my old
ones, many have not been fixed in this series yet.

Thanks,
Andrew

> 
> On Thu, Mar 9, 2017 at 4:27 PM, Liam Breck <liam@networkimprov.net> wrote:
>> From: Liam Breck <kernel@networkimprov.net>
>>
>> Previously there was no way to configure chip registers in the event that the
>> defaults didn't match the battery in question.
>>
>> BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
>> and writes battery data to chip RAM or non-volatile memory.
>>
>> Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.
>>
>> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
>> Signed-off-by: Liam Breck <kernel@networkimprov.net>
>> ---
>>  drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
>>  include/linux/power/bq27xxx_battery.h  |   2 +
>>  2 files changed, 468 insertions(+), 10 deletions(-)
>>
>> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
>> index 8ce2940..f382d18 100644
>> --- a/drivers/power/supply/bq27xxx_battery.c
>> +++ b/drivers/power/supply/bq27xxx_battery.c
>> @@ -51,7 +51,7 @@
>>
>>  #include <linux/power/bq27xxx_battery.h>
>>
>> -#define DRIVER_VERSION         "1.2.0"
>> +#define DRIVER_VERSION         "1.3.0"
>>
>>  #define BQ27XXX_MANUFACTURER   "Texas Instruments"
>>
>> @@ -59,6 +59,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)
>> @@ -72,6 +73,11 @@
>>  #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_RS                     (20) /* Resistor sense mOhm */
>>  #define BQ27XXX_POWER_CONSTANT         (29200) /* 29.2 µV^2 * 1000 */
>>  #define BQ27XXX_CURRENT_CONSTANT       (3570) /* 3.57 µV * 1000 */
>> @@ -102,6 +108,11 @@ enum bq27xxx_reg_index {
>>         BQ27XXX_REG_SOC,        /* State-of-Charge */
>>         BQ27XXX_REG_DCAP,       /* Design Capacity */
>>         BQ27XXX_REG_AP,         /* Average Power */
>> +       BQ27XXX_DM_CTRL,        /* BlockDataControl() */
>> +       BQ27XXX_DM_CLASS,       /* DataClass() */
>> +       BQ27XXX_DM_BLOCK,       /* DataBlock() */
>> +       BQ27XXX_DM_DATA,        /* BlockData() */
>> +       BQ27XXX_DM_CKSUM,       /* BlockDataChecksum() */
>>         BQ27XXX_REG_MAX,        /* sentinel */
>>  };
>>
>> @@ -125,6 +136,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,
>> @@ -144,6 +160,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,
>>         },
>>         [BQ27500] = {
>>                 [BQ27XXX_REG_CTRL] = 0x00,
>> @@ -163,6 +184,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,
>>         },
>>         [BQ27510] = {
>>                 [BQ27XXX_REG_CTRL] = 0x00,
>> @@ -182,6 +208,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,
>>         },
>>         [BQ27530] = {
>>                 [BQ27XXX_REG_CTRL] = 0x00,
>> @@ -201,6 +232,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,
>> @@ -220,6 +256,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,
>> @@ -239,6 +280,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,
>> @@ -258,6 +304,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,
>>         },
>>  };
>>
>> @@ -432,6 +483,82 @@ static struct {
>>  static DEFINE_MUTEX(bq27xxx_list_lock);
>>  static LIST_HEAD(bq27xxx_battery_devices);
>>
>> +#define BQ27XXX_DM_SZ  32
>> +
>> +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
>> +
>> +struct bq27xxx_dm_reg {
>> +       u8 subclass_id;
>> +       u8 offset;
>> +       u8 bytes;
>> +       u16 min, max;
>> +};
>> +
>> +struct bq27xxx_dm_buf {
>> +       u8 class;
>> +       u8 block;
>> +       u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
>>  {
>>         struct bq27xxx_device_info *di;
>> @@ -476,6 +603,316 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
>>         return di->bus.read(di, di->regs[reg_index], single);
>>  }
>>
>> +static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
>> +                                         bool state)
>> +{
>> +       int ret;
>> +
>> +       if (state) {
>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
>> +               if (ret < 0)
>> +                       goto out;
>> +       } else {
>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
>> +               if (ret < 0)
>> +                       goto out;
>> +
>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
>> +               if (ret < 0)
>> +                       goto out;
>> +       }
>> +       return 0;
>> +
>> +out:
>> +       dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       BQ27XXX_MSLEEP(1);
>> +
>> +       ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       ret = di->bus.read(di, di->regs[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,
>> +                                        bool state)
>> +{
>> +       int ret, try=100;
>> +
>> +       ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */
>> +               dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
>> +
>> +       if (try)
>> +               return 0;
>> +
>> +       dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
>> +       return -EINVAL;
>> +
>> +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->updt)
>> +               return 0;
>> +
>> +       if (cfgup) {
>> +               ret = bq27xxx_battery_set_cfgupdate(di, true);
>> +               if (ret < 0)
>> +                       return ret;
>> +       }
>> +
>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       BQ27XXX_MSLEEP(1);
>> +
>> +       ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
>> +                           bq27xxx_battery_checksum_dm_block(buf), true);
>> +       if (ret < 0)
>> +               goto out;
>> +
>> +       /* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
>> +        * If the 'time' delay is insufficient, NVM corruption results on
>> +        * the '425 chip (and perhaps others), which could damage the chip.
>> +        * It was suggested in this TI tool:
>> +        *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
>> +        *
>> +        * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
>> +        * 2. write(BQ27XXX_DM_BLOCK, buf->block)
>> +        * 3. sum = read(BQ27XXX_DM_CKSUM)
>> +        * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
>> +        *      report error
>> +        */
>> +
>> +       if (cfgup) {
>> +               BQ27XXX_MSLEEP(1);
>> +               ret = bq27xxx_battery_set_cfgupdate(di, false);
>> +               if (ret < 0)
>> +                       return ret;
>> +       } else {
>> +               /* flash DM updates in <100ms, but reset time isn't documented */
>> +               BQ27XXX_MSLEEP(400);
>> +       }
>> +
>> +       buf->updt = false;
>> +       return 0;
>> +
>> +out:
>> +       if (cfgup)
>> +               bq27xxx_battery_set_cfgupdate(di, false);
>> +
>> +       dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
>> +       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 (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);
>> +}
>> +
>> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
>> +{
>> +       struct power_supply_battery_info info = {};
>> +       unsigned int min, max;
>> +
>> +       /* functions don't exist for writing data so abort */
>> +       if (!di->bus.write || !di->bus.write_bulk)
>> +               return;
>> +
>> +       /* no settings to be set for this chipset so abort */
>> +       if (!di->dm_regs)
>> +               return;
>> +
>> +       if (bq27xxx_battery_set_seal_state(di, false) < 0)
>> +               return;
>> +
>> +       if (power_supply_get_battery_info(di->bat, &info) < 0)
>> +               goto out;
>> +
>> +       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)
>> +               goto out;
>> +
>> +       bq27xxx_battery_set_config(di, &info);
>> +
>> +out:
>> +       bq27xxx_battery_set_seal_state(di, true);
>> +}
>> +
>>  /*
>>   * Return the battery State-of-Charge
>>   * Or < 0 if something fails.
>> @@ -985,6 +1422,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;
>> @@ -1015,20 +1459,31 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
>>         schedule_delayed_work(&di->work, 0);
>>  }
>>
>> +#define BQ27XXX_INIT(c,d,k)   \
>> +       di->chip       = (c); \
>> +       di->dm_regs    = (d); \
>> +       di->unseal_key = (k)
>> +
>>  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,
>> +       };
>>
>>         switch(di->chip) {
>> -       case BQ27520: di->chip = BQ27510; break;
>> -       case BQ27531: di->chip = BQ27530; break;
>> -       case BQ27542: di->chip = BQ27541; break;
>> -       case BQ27546: di->chip = BQ27541; break;
>> -       case BQ27742: di->chip = BQ27541; break;
>> -       case BQ27425: di->chip = BQ27421; break;
>> -       case BQ27441: di->chip = BQ27421; break;
>> -       case BQ27621: di->chip = BQ27421; break;
>> +       case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
>> +       case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
>> +       case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
>> +       case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
>> +       case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
>> +       case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
>> +       case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
>> +       case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>> +       case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
>> +       case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>> +       case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
>>         default: break;
>>         }
>>
>> @@ -1055,6 +1510,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);
>> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
>> index 90b7ca1..9a1c30b 100644
>> --- a/include/linux/power/bq27xxx_battery.h
>> +++ b/include/linux/power/bq27xxx_battery.h
>> @@ -66,6 +66,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;
>> --
>> 2.9.3
>>
Liam Breck March 10, 2017, 9:49 p.m. UTC | #3
On Fri, Mar 10, 2017 at 1:25 PM, Andrew F. Davis <afd@ti.com> wrote:
> On 03/10/2017 03:01 PM, Liam Breck wrote:
>> Andrew, any comments on these last two drafts?
>>
>
> 1) Don't ping maintainers/reviewers, I haven't forgotten about you, it
> hasn't been even 24 hours since you posted this.

Sorry. You did say at start of week that you'd have some time to test
this patchset.

> 2) I have lots of comments, but it's Friday and I don't feel like
> reviewing stuff, if you want some comments to work on go read my old
> ones, many have not been fixed in this series yet.

I believe I have addressed all your comments. I didn't fix things
where the comment missed my rationale, but I did clarify the
rationale.

>> On Thu, Mar 9, 2017 at 4:27 PM, Liam Breck <liam@networkimprov.net> wrote:
>>> From: Liam Breck <kernel@networkimprov.net>
>>>
>>> Previously there was no way to configure chip registers in the event that the
>>> defaults didn't match the battery in question.
>>>
>>> BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
>>> and writes battery data to chip RAM or non-volatile memory.
>>>
>>> Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.
>>>
>>> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
>>> Signed-off-by: Liam Breck <kernel@networkimprov.net>
>>> ---
>>>  drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
>>>  include/linux/power/bq27xxx_battery.h  |   2 +
>>>  2 files changed, 468 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
>>> index 8ce2940..f382d18 100644
>>> --- a/drivers/power/supply/bq27xxx_battery.c
>>> +++ b/drivers/power/supply/bq27xxx_battery.c
>>> @@ -51,7 +51,7 @@
>>>
>>>  #include <linux/power/bq27xxx_battery.h>
>>>
>>> -#define DRIVER_VERSION         "1.2.0"
>>> +#define DRIVER_VERSION         "1.3.0"
>>>
>>>  #define BQ27XXX_MANUFACTURER   "Texas Instruments"
>>>
>>> @@ -59,6 +59,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)
>>> @@ -72,6 +73,11 @@
>>>  #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_RS                     (20) /* Resistor sense mOhm */
>>>  #define BQ27XXX_POWER_CONSTANT         (29200) /* 29.2 µV^2 * 1000 */
>>>  #define BQ27XXX_CURRENT_CONSTANT       (3570) /* 3.57 µV * 1000 */
>>> @@ -102,6 +108,11 @@ enum bq27xxx_reg_index {
>>>         BQ27XXX_REG_SOC,        /* State-of-Charge */
>>>         BQ27XXX_REG_DCAP,       /* Design Capacity */
>>>         BQ27XXX_REG_AP,         /* Average Power */
>>> +       BQ27XXX_DM_CTRL,        /* BlockDataControl() */
>>> +       BQ27XXX_DM_CLASS,       /* DataClass() */
>>> +       BQ27XXX_DM_BLOCK,       /* DataBlock() */
>>> +       BQ27XXX_DM_DATA,        /* BlockData() */
>>> +       BQ27XXX_DM_CKSUM,       /* BlockDataChecksum() */
>>>         BQ27XXX_REG_MAX,        /* sentinel */
>>>  };
>>>
>>> @@ -125,6 +136,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,
>>> @@ -144,6 +160,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,
>>>         },
>>>         [BQ27500] = {
>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>> @@ -163,6 +184,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,
>>>         },
>>>         [BQ27510] = {
>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>> @@ -182,6 +208,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,
>>>         },
>>>         [BQ27530] = {
>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>> @@ -201,6 +232,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,
>>> @@ -220,6 +256,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,
>>> @@ -239,6 +280,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,
>>> @@ -258,6 +304,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,
>>>         },
>>>  };
>>>
>>> @@ -432,6 +483,82 @@ static struct {
>>>  static DEFINE_MUTEX(bq27xxx_list_lock);
>>>  static LIST_HEAD(bq27xxx_battery_devices);
>>>
>>> +#define BQ27XXX_DM_SZ  32
>>> +
>>> +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
>>> +
>>> +struct bq27xxx_dm_reg {
>>> +       u8 subclass_id;
>>> +       u8 offset;
>>> +       u8 bytes;
>>> +       u16 min, max;
>>> +};
>>> +
>>> +struct bq27xxx_dm_buf {
>>> +       u8 class;
>>> +       u8 block;
>>> +       u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
>>>  {
>>>         struct bq27xxx_device_info *di;
>>> @@ -476,6 +603,316 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
>>>         return di->bus.read(di, di->regs[reg_index], single);
>>>  }
>>>
>>> +static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
>>> +                                         bool state)
>>> +{
>>> +       int ret;
>>> +
>>> +       if (state) {
>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
>>> +               if (ret < 0)
>>> +                       goto out;
>>> +       } else {
>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
>>> +               if (ret < 0)
>>> +                       goto out;
>>> +
>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
>>> +               if (ret < 0)
>>> +                       goto out;
>>> +       }
>>> +       return 0;
>>> +
>>> +out:
>>> +       dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       BQ27XXX_MSLEEP(1);
>>> +
>>> +       ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       ret = di->bus.read(di, di->regs[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,
>>> +                                        bool state)
>>> +{
>>> +       int ret, try=100;
>>> +
>>> +       ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */
>>> +               dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
>>> +
>>> +       if (try)
>>> +               return 0;
>>> +
>>> +       dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
>>> +       return -EINVAL;
>>> +
>>> +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->updt)
>>> +               return 0;
>>> +
>>> +       if (cfgup) {
>>> +               ret = bq27xxx_battery_set_cfgupdate(di, true);
>>> +               if (ret < 0)
>>> +                       return ret;
>>> +       }
>>> +
>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       BQ27XXX_MSLEEP(1);
>>> +
>>> +       ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
>>> +                           bq27xxx_battery_checksum_dm_block(buf), true);
>>> +       if (ret < 0)
>>> +               goto out;
>>> +
>>> +       /* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
>>> +        * If the 'time' delay is insufficient, NVM corruption results on
>>> +        * the '425 chip (and perhaps others), which could damage the chip.
>>> +        * It was suggested in this TI tool:
>>> +        *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
>>> +        *
>>> +        * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
>>> +        * 2. write(BQ27XXX_DM_BLOCK, buf->block)
>>> +        * 3. sum = read(BQ27XXX_DM_CKSUM)
>>> +        * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
>>> +        *      report error
>>> +        */
>>> +
>>> +       if (cfgup) {
>>> +               BQ27XXX_MSLEEP(1);
>>> +               ret = bq27xxx_battery_set_cfgupdate(di, false);
>>> +               if (ret < 0)
>>> +                       return ret;
>>> +       } else {
>>> +               /* flash DM updates in <100ms, but reset time isn't documented */
>>> +               BQ27XXX_MSLEEP(400);
>>> +       }
>>> +
>>> +       buf->updt = false;
>>> +       return 0;
>>> +
>>> +out:
>>> +       if (cfgup)
>>> +               bq27xxx_battery_set_cfgupdate(di, false);
>>> +
>>> +       dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
>>> +       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 (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);
>>> +}
>>> +
>>> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
>>> +{
>>> +       struct power_supply_battery_info info = {};
>>> +       unsigned int min, max;
>>> +
>>> +       /* functions don't exist for writing data so abort */
>>> +       if (!di->bus.write || !di->bus.write_bulk)
>>> +               return;
>>> +
>>> +       /* no settings to be set for this chipset so abort */
>>> +       if (!di->dm_regs)
>>> +               return;
>>> +
>>> +       if (bq27xxx_battery_set_seal_state(di, false) < 0)
>>> +               return;
>>> +
>>> +       if (power_supply_get_battery_info(di->bat, &info) < 0)
>>> +               goto out;
>>> +
>>> +       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)
>>> +               goto out;
>>> +
>>> +       bq27xxx_battery_set_config(di, &info);
>>> +
>>> +out:
>>> +       bq27xxx_battery_set_seal_state(di, true);
>>> +}
>>> +
>>>  /*
>>>   * Return the battery State-of-Charge
>>>   * Or < 0 if something fails.
>>> @@ -985,6 +1422,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;
>>> @@ -1015,20 +1459,31 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
>>>         schedule_delayed_work(&di->work, 0);
>>>  }
>>>
>>> +#define BQ27XXX_INIT(c,d,k)   \
>>> +       di->chip       = (c); \
>>> +       di->dm_regs    = (d); \
>>> +       di->unseal_key = (k)
>>> +
>>>  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,
>>> +       };
>>>
>>>         switch(di->chip) {
>>> -       case BQ27520: di->chip = BQ27510; break;
>>> -       case BQ27531: di->chip = BQ27530; break;
>>> -       case BQ27542: di->chip = BQ27541; break;
>>> -       case BQ27546: di->chip = BQ27541; break;
>>> -       case BQ27742: di->chip = BQ27541; break;
>>> -       case BQ27425: di->chip = BQ27421; break;
>>> -       case BQ27441: di->chip = BQ27421; break;
>>> -       case BQ27621: di->chip = BQ27421; break;
>>> +       case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
>>> +       case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
>>> +       case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
>>> +       case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
>>> +       case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>> +       case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>> +       case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>> +       case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>> +       case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
>>> +       case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>> +       case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
>>>         default: break;
>>>         }
>>>
>>> @@ -1055,6 +1510,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);
>>> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
>>> index 90b7ca1..9a1c30b 100644
>>> --- a/include/linux/power/bq27xxx_battery.h
>>> +++ b/include/linux/power/bq27xxx_battery.h
>>> @@ -66,6 +66,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;
>>> --
>>> 2.9.3
>>>
Andrew Davis March 13, 2017, 5:53 p.m. UTC | #4
On 03/10/2017 03:49 PM, Liam Breck wrote:
> On Fri, Mar 10, 2017 at 1:25 PM, Andrew F. Davis <afd@ti.com> wrote:
>> On 03/10/2017 03:01 PM, Liam Breck wrote:
>>> Andrew, any comments on these last two drafts?
>>>
>>
>> 1) Don't ping maintainers/reviewers, I haven't forgotten about you, it
>> hasn't been even 24 hours since you posted this.
> 
> Sorry. You did say at start of week that you'd have some time to test
> this patchset.

Yes, when it is done, lets fix the code first, *then* make sure the code
works :)

> 
>> 2) I have lots of comments, but it's Friday and I don't feel like
>> reviewing stuff, if you want some comments to work on go read my old
>> ones, many have not been fixed in this series yet.
> 
> I believe I have addressed all your comments. I didn't fix things
> where the comment missed my rationale, but I did clarify the
> rationale.
> 
>>> On Thu, Mar 9, 2017 at 4:27 PM, Liam Breck <liam@networkimprov.net> wrote:
>>>> From: Liam Breck <kernel@networkimprov.net>
>>>>
>>>> Previously there was no way to configure chip registers in the event that the
>>>> defaults didn't match the battery in question.
>>>>
>>>> BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
>>>> and writes battery data to chip RAM or non-volatile memory.
>>>>
>>>> Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.
>>>>
>>>> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
>>>> Signed-off-by: Liam Breck <kernel@networkimprov.net>
>>>> ---
>>>>  drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
>>>>  include/linux/power/bq27xxx_battery.h  |   2 +
>>>>  2 files changed, 468 insertions(+), 10 deletions(-)
>>>>
>>>> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
>>>> index 8ce2940..f382d18 100644
>>>> --- a/drivers/power/supply/bq27xxx_battery.c
>>>> +++ b/drivers/power/supply/bq27xxx_battery.c
>>>> @@ -51,7 +51,7 @@
>>>>
>>>>  #include <linux/power/bq27xxx_battery.h>
>>>>
>>>> -#define DRIVER_VERSION         "1.2.0"
>>>> +#define DRIVER_VERSION         "1.3.0"
>>>>
>>>>  #define BQ27XXX_MANUFACTURER   "Texas Instruments"
>>>>
>>>> @@ -59,6 +59,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)
>>>> @@ -72,6 +73,11 @@
>>>>  #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_RS                     (20) /* Resistor sense mOhm */
>>>>  #define BQ27XXX_POWER_CONSTANT         (29200) /* 29.2 µV^2 * 1000 */
>>>>  #define BQ27XXX_CURRENT_CONSTANT       (3570) /* 3.57 µV * 1000 */
>>>> @@ -102,6 +108,11 @@ enum bq27xxx_reg_index {
>>>>         BQ27XXX_REG_SOC,        /* State-of-Charge */
>>>>         BQ27XXX_REG_DCAP,       /* Design Capacity */
>>>>         BQ27XXX_REG_AP,         /* Average Power */
>>>> +       BQ27XXX_DM_CTRL,        /* BlockDataControl() */
>>>> +       BQ27XXX_DM_CLASS,       /* DataClass() */
>>>> +       BQ27XXX_DM_BLOCK,       /* DataBlock() */
>>>> +       BQ27XXX_DM_DATA,        /* BlockData() */
>>>> +       BQ27XXX_DM_CKSUM,       /* BlockDataChecksum() */
>>>>         BQ27XXX_REG_MAX,        /* sentinel */
>>>>  };
>>>>
>>>> @@ -125,6 +136,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,
>>>> @@ -144,6 +160,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,
>>>>         },
>>>>         [BQ27500] = {
>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>> @@ -163,6 +184,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,
>>>>         },
>>>>         [BQ27510] = {
>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>> @@ -182,6 +208,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,
>>>>         },
>>>>         [BQ27530] = {
>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>> @@ -201,6 +232,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,
>>>> @@ -220,6 +256,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,
>>>> @@ -239,6 +280,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,
>>>> @@ -258,6 +304,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,
>>>>         },
>>>>  };
>>>>
>>>> @@ -432,6 +483,82 @@ static struct {
>>>>  static DEFINE_MUTEX(bq27xxx_list_lock);
>>>>  static LIST_HEAD(bq27xxx_battery_devices);
>>>>
>>>> +#define BQ27XXX_DM_SZ  32
>>>> +
>>>> +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
>>>> +

This still doesn't seem right, is there any precedent for such a thing
elsewhere in the kernel?

>>>> +struct bq27xxx_dm_reg {
>>>> +       u8 subclass_id;
>>>> +       u8 offset;
>>>> +       u8 bytes;
>>>> +       u16 min, max;
>>>> +};
>>>> +
>>>> +struct bq27xxx_dm_buf {
>>>> +       u8 class;
>>>> +       u8 block;
>>>> +       u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
>>>>  {
>>>>         struct bq27xxx_device_info *di;
>>>> @@ -476,6 +603,316 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
>>>>         return di->bus.read(di, di->regs[reg_index], single);
>>>>  }
>>>>
>>>> +static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
>>>> +                                         bool state)
>>>> +{
>>>> +       int ret;
>>>> +
>>>> +       if (state) {
>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
>>>> +               if (ret < 0)
>>>> +                       goto out;
>>>> +       } else {
>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
>>>> +               if (ret < 0)
>>>> +                       goto out;
>>>> +
>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
>>>> +               if (ret < 0)
>>>> +                       goto out;
>>>> +       }
>>>> +       return 0;
>>>> +
>>>> +out:
>>>> +       dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       BQ27XXX_MSLEEP(1);
>>>> +
>>>> +       ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       ret = di->bus.read(di, di->regs[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,
>>>> +                                        bool state)
>>>> +{
>>>> +       int ret, try=100;
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */

If this isn't an RFC we shouldn't be making comments for future version
of this patchset.

>>>> +               dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
>>>> +
>>>> +       if (try)
>>>> +               return 0;
>>>> +
>>>> +       dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
>>>> +       return -EINVAL;
>>>> +
>>>> +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->updt)
>>>> +               return 0;
>>>> +
>>>> +       if (cfgup) {
>>>> +               ret = bq27xxx_battery_set_cfgupdate(di, true);
>>>> +               if (ret < 0)
>>>> +                       return ret;
>>>> +       }
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       BQ27XXX_MSLEEP(1);
>>>> +
>>>> +       ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
>>>> +                           bq27xxx_battery_checksum_dm_block(buf), true);
>>>> +       if (ret < 0)
>>>> +               goto out;
>>>> +
>>>> +       /* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
>>>> +        * If the 'time' delay is insufficient, NVM corruption results on
>>>> +        * the '425 chip (and perhaps others), which could damage the chip.
>>>> +        * It was suggested in this TI tool:
>>>> +        *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
>>>> +        *
>>>> +        * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
>>>> +        * 2. write(BQ27XXX_DM_BLOCK, buf->block)
>>>> +        * 3. sum = read(BQ27XXX_DM_CKSUM)
>>>> +        * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
>>>> +        *      report error
>>>> +        */
>>>> +
>>>> +       if (cfgup) {
>>>> +               BQ27XXX_MSLEEP(1);
>>>> +               ret = bq27xxx_battery_set_cfgupdate(di, false);
>>>> +               if (ret < 0)
>>>> +                       return ret;
>>>> +       } else {
>>>> +               /* flash DM updates in <100ms, but reset time isn't documented */
>>>> +               BQ27XXX_MSLEEP(400);
>>>> +       }
>>>> +
>>>> +       buf->updt = false;
>>>> +       return 0;
>>>> +
>>>> +out:
>>>> +       if (cfgup)
>>>> +               bq27xxx_battery_set_cfgupdate(di, false);
>>>> +
>>>> +       dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
>>>> +       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 (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);
>>>> +}
>>>> +
>>>> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
>>>> +{
>>>> +       struct power_supply_battery_info info = {};
>>>> +       unsigned int min, max;
>>>> +
>>>> +       /* functions don't exist for writing data so abort */
>>>> +       if (!di->bus.write || !di->bus.write_bulk)
>>>> +               return;
>>>> +
>>>> +       /* no settings to be set for this chipset so abort */
>>>> +       if (!di->dm_regs)
>>>> +               return;
>>>> +
>>>> +       if (bq27xxx_battery_set_seal_state(di, false) < 0)
>>>> +               return;
>>>> +
>>>> +       if (power_supply_get_battery_info(di->bat, &info) < 0)
>>>> +               goto out;
>>>> +
>>>> +       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)
>>>> +               goto out;
>>>> +
>>>> +       bq27xxx_battery_set_config(di, &info);
>>>> +
>>>> +out:
>>>> +       bq27xxx_battery_set_seal_state(di, true);
>>>> +}
>>>> +
>>>>  /*
>>>>   * Return the battery State-of-Charge
>>>>   * Or < 0 if something fails.
>>>> @@ -985,6 +1422,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;
>>>> @@ -1015,20 +1459,31 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
>>>>         schedule_delayed_work(&di->work, 0);
>>>>  }
>>>>
>>>> +#define BQ27XXX_INIT(c,d,k)   \
>>>> +       di->chip       = (c); \
>>>> +       di->dm_regs    = (d); \
>>>> +       di->unseal_key = (k)
>>>> +
>>>>  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,
>>>> +       };
>>>>
>>>>         switch(di->chip) {
>>>> -       case BQ27520: di->chip = BQ27510; break;
>>>> -       case BQ27531: di->chip = BQ27530; break;
>>>> -       case BQ27542: di->chip = BQ27541; break;
>>>> -       case BQ27546: di->chip = BQ27541; break;
>>>> -       case BQ27742: di->chip = BQ27541; break;
>>>> -       case BQ27425: di->chip = BQ27421; break;
>>>> -       case BQ27441: di->chip = BQ27421; break;
>>>> -       case BQ27621: di->chip = BQ27421; break;

You just added this in the last patch, now you are removing it... lets
reorder some of this.

>>>> +       case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
>>>> +       case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
>>>> +       case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
>>>> +       case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
>>>> +       case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>> +       case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>> +       case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>> +       case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>>> +       case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
>>>> +       case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>>> +       case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
>>>>         default: break;
>>>>         }
>>>>
>>>> @@ -1055,6 +1510,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);
>>>> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
>>>> index 90b7ca1..9a1c30b 100644
>>>> --- a/include/linux/power/bq27xxx_battery.h
>>>> +++ b/include/linux/power/bq27xxx_battery.h
>>>> @@ -66,6 +66,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;
>>>> --
>>>> 2.9.3
>>>>
Liam Breck March 13, 2017, 7:57 p.m. UTC | #5
On Mon, Mar 13, 2017 at 10:53 AM, Andrew F. Davis <afd@ti.com> wrote:
> On 03/10/2017 03:49 PM, Liam Breck wrote:
>> On Fri, Mar 10, 2017 at 1:25 PM, Andrew F. Davis <afd@ti.com> wrote:
>>> On 03/10/2017 03:01 PM, Liam Breck wrote:
>>>> Andrew, any comments on these last two drafts?
>>>>
>>>
>>> 1) Don't ping maintainers/reviewers, I haven't forgotten about you, it
>>> hasn't been even 24 hours since you posted this.
>>
>> Sorry. You did say at start of week that you'd have some time to test
>> this patchset.
>
> Yes, when it is done, lets fix the code first, *then* make sure the code
> works :)

I'm not sure that minimizing the number of test passes is the best
strategy, unless you assume I'm infallible :-)

>>> 2) I have lots of comments, but it's Friday and I don't feel like
>>> reviewing stuff, if you want some comments to work on go read my old
>>> ones, many have not been fixed in this series yet.
>>
>> I believe I have addressed all your comments. I didn't fix things
>> where the comment missed my rationale, but I did clarify the
>> rationale.
>>
>>>> On Thu, Mar 9, 2017 at 4:27 PM, Liam Breck <liam@networkimprov.net> wrote:
>>>>> From: Liam Breck <kernel@networkimprov.net>
>>>>>
>>>>> Previously there was no way to configure chip registers in the event that the
>>>>> defaults didn't match the battery in question.
>>>>>
>>>>> BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs,
>>>>> and writes battery data to chip RAM or non-volatile memory.
>>>>>
>>>>> Supports chips BQ27500, 545, 421, 425, 441, 621. Others may be added.
>>>>>
>>>>> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
>>>>> Signed-off-by: Liam Breck <kernel@networkimprov.net>
>>>>> ---
>>>>>  drivers/power/supply/bq27xxx_battery.c | 476 ++++++++++++++++++++++++++++++++-
>>>>>  include/linux/power/bq27xxx_battery.h  |   2 +
>>>>>  2 files changed, 468 insertions(+), 10 deletions(-)
>>>>>
>>>>> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
>>>>> index 8ce2940..f382d18 100644
>>>>> --- a/drivers/power/supply/bq27xxx_battery.c
>>>>> +++ b/drivers/power/supply/bq27xxx_battery.c
>>>>> @@ -51,7 +51,7 @@
>>>>>
>>>>>  #include <linux/power/bq27xxx_battery.h>
>>>>>
>>>>> -#define DRIVER_VERSION         "1.2.0"
>>>>> +#define DRIVER_VERSION         "1.3.0"
>>>>>
>>>>>  #define BQ27XXX_MANUFACTURER   "Texas Instruments"
>>>>>
>>>>> @@ -59,6 +59,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)
>>>>> @@ -72,6 +73,11 @@
>>>>>  #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_RS                     (20) /* Resistor sense mOhm */
>>>>>  #define BQ27XXX_POWER_CONSTANT         (29200) /* 29.2 µV^2 * 1000 */
>>>>>  #define BQ27XXX_CURRENT_CONSTANT       (3570) /* 3.57 µV * 1000 */
>>>>> @@ -102,6 +108,11 @@ enum bq27xxx_reg_index {
>>>>>         BQ27XXX_REG_SOC,        /* State-of-Charge */
>>>>>         BQ27XXX_REG_DCAP,       /* Design Capacity */
>>>>>         BQ27XXX_REG_AP,         /* Average Power */
>>>>> +       BQ27XXX_DM_CTRL,        /* BlockDataControl() */
>>>>> +       BQ27XXX_DM_CLASS,       /* DataClass() */
>>>>> +       BQ27XXX_DM_BLOCK,       /* DataBlock() */
>>>>> +       BQ27XXX_DM_DATA,        /* BlockData() */
>>>>> +       BQ27XXX_DM_CKSUM,       /* BlockDataChecksum() */
>>>>>         BQ27XXX_REG_MAX,        /* sentinel */
>>>>>  };
>>>>>
>>>>> @@ -125,6 +136,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,
>>>>> @@ -144,6 +160,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,
>>>>>         },
>>>>>         [BQ27500] = {
>>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>>> @@ -163,6 +184,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,
>>>>>         },
>>>>>         [BQ27510] = {
>>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>>> @@ -182,6 +208,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,
>>>>>         },
>>>>>         [BQ27530] = {
>>>>>                 [BQ27XXX_REG_CTRL] = 0x00,
>>>>> @@ -201,6 +232,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,
>>>>> @@ -220,6 +256,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,
>>>>> @@ -239,6 +280,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,
>>>>> @@ -258,6 +304,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,
>>>>>         },
>>>>>  };
>>>>>
>>>>> @@ -432,6 +483,82 @@ static struct {
>>>>>  static DEFINE_MUTEX(bq27xxx_list_lock);
>>>>>  static LIST_HEAD(bq27xxx_battery_devices);
>>>>>
>>>>> +#define BQ27XXX_DM_SZ  32
>>>>> +
>>>>> +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
>>>>> +
>
> This still doesn't seem right, is there any precedent for such a thing
> elsewhere in the kernel?

I didn't see any in 4.9 tree, but most drivers don't accommodate so
many diff parts. We may need to adjust the +500 as we add chips, and a
statement macro is cleaner than:
  usleep_range(10*1000, 10*1000+BQ27XXX_SLEEP_DELAY);

>>>>> +struct bq27xxx_dm_reg {
>>>>> +       u8 subclass_id;
>>>>> +       u8 offset;
>>>>> +       u8 bytes;
>>>>> +       u16 min, max;
>>>>> +};
>>>>> +
>>>>> +struct bq27xxx_dm_buf {
>>>>> +       u8 class;
>>>>> +       u8 block;
>>>>> +       u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
>>>>>  {
>>>>>         struct bq27xxx_device_info *di;
>>>>> @@ -476,6 +603,316 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
>>>>>         return di->bus.read(di, di->regs[reg_index], single);
>>>>>  }
>>>>>
>>>>> +static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
>>>>> +                                         bool state)
>>>>> +{
>>>>> +       int ret;
>>>>> +
>>>>> +       if (state) {
>>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
>>>>> +               if (ret < 0)
>>>>> +                       goto out;
>>>>> +       } else {
>>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
>>>>> +               if (ret < 0)
>>>>> +                       goto out;
>>>>> +
>>>>> +               ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
>>>>> +               if (ret < 0)
>>>>> +                       goto out;
>>>>> +       }
>>>>> +       return 0;
>>>>> +
>>>>> +out:
>>>>> +       dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       BQ27XXX_MSLEEP(1);
>>>>> +
>>>>> +       ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = di->bus.read(di, di->regs[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,
>>>>> +                                        bool state)
>>>>> +{
>>>>> +       int ret, try=100;
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */
>
> If this isn't an RFC we shouldn't be making comments for future version
> of this patchset.

I'll turn this into a dev_warn() for more than 3 retries.

>>>>> +               dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
>>>>> +
>>>>> +       if (try)
>>>>> +               return 0;
>>>>> +
>>>>> +       dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
>>>>> +       return -EINVAL;
>>>>> +
>>>>> +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->updt)
>>>>> +               return 0;
>>>>> +
>>>>> +       if (cfgup) {
>>>>> +               ret = bq27xxx_battery_set_cfgupdate(di, true);
>>>>> +               if (ret < 0)
>>>>> +                       return ret;
>>>>> +       }
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       BQ27XXX_MSLEEP(1);
>>>>> +
>>>>> +       ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
>>>>> +                           bq27xxx_battery_checksum_dm_block(buf), true);
>>>>> +       if (ret < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       /* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
>>>>> +        * If the 'time' delay is insufficient, NVM corruption results on
>>>>> +        * the '425 chip (and perhaps others), which could damage the chip.
>>>>> +        * It was suggested in this TI tool:
>>>>> +        *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
>>>>> +        *
>>>>> +        * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
>>>>> +        * 2. write(BQ27XXX_DM_BLOCK, buf->block)
>>>>> +        * 3. sum = read(BQ27XXX_DM_CKSUM)
>>>>> +        * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
>>>>> +        *      report error
>>>>> +        */
>>>>> +
>>>>> +       if (cfgup) {
>>>>> +               BQ27XXX_MSLEEP(1);
>>>>> +               ret = bq27xxx_battery_set_cfgupdate(di, false);
>>>>> +               if (ret < 0)
>>>>> +                       return ret;
>>>>> +       } else {
>>>>> +               /* flash DM updates in <100ms, but reset time isn't documented */
>>>>> +               BQ27XXX_MSLEEP(400);
>>>>> +       }
>>>>> +
>>>>> +       buf->updt = false;
>>>>> +       return 0;
>>>>> +
>>>>> +out:
>>>>> +       if (cfgup)
>>>>> +               bq27xxx_battery_set_cfgupdate(di, false);
>>>>> +
>>>>> +       dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
>>>>> +       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 (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);
>>>>> +}
>>>>> +
>>>>> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
>>>>> +{
>>>>> +       struct power_supply_battery_info info = {};
>>>>> +       unsigned int min, max;
>>>>> +
>>>>> +       /* functions don't exist for writing data so abort */
>>>>> +       if (!di->bus.write || !di->bus.write_bulk)
>>>>> +               return;
>>>>> +
>>>>> +       /* no settings to be set for this chipset so abort */
>>>>> +       if (!di->dm_regs)
>>>>> +               return;
>>>>> +
>>>>> +       if (bq27xxx_battery_set_seal_state(di, false) < 0)
>>>>> +               return;
>>>>> +
>>>>> +       if (power_supply_get_battery_info(di->bat, &info) < 0)
>>>>> +               goto out;
>>>>> +
>>>>> +       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)
>>>>> +               goto out;
>>>>> +
>>>>> +       bq27xxx_battery_set_config(di, &info);
>>>>> +
>>>>> +out:
>>>>> +       bq27xxx_battery_set_seal_state(di, true);
>>>>> +}
>>>>> +
>>>>>  /*
>>>>>   * Return the battery State-of-Charge
>>>>>   * Or < 0 if something fails.
>>>>> @@ -985,6 +1422,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;
>>>>> @@ -1015,20 +1459,31 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
>>>>>         schedule_delayed_work(&di->work, 0);
>>>>>  }
>>>>>
>>>>> +#define BQ27XXX_INIT(c,d,k)   \
>>>>> +       di->chip       = (c); \
>>>>> +       di->dm_regs    = (d); \
>>>>> +       di->unseal_key = (k)
>>>>> +
>>>>>  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,
>>>>> +       };
>>>>>
>>>>>         switch(di->chip) {
>>>>> -       case BQ27520: di->chip = BQ27510; break;
>>>>> -       case BQ27531: di->chip = BQ27530; break;
>>>>> -       case BQ27542: di->chip = BQ27541; break;
>>>>> -       case BQ27546: di->chip = BQ27541; break;
>>>>> -       case BQ27742: di->chip = BQ27541; break;
>>>>> -       case BQ27425: di->chip = BQ27421; break;
>>>>> -       case BQ27441: di->chip = BQ27421; break;
>>>>> -       case BQ27621: di->chip = BQ27421; break;
>
> You just added this in the last patch, now you are removing it... lets
> reorder some of this.

Those are necessary for the previous patch to work on its own.

I'll keep the above instead of BQ27XXX_INIT(ID, 0, 0) below, and add
no-op cases for the other IDs.

>>>>> +       case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
>>>>> +       case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
>>>>> +       case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
>>>>> +       case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
>>>>> +       case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>>> +       case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>>> +       case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
>>>>> +       case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>>>> +       case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
>>>>> +       case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
>>>>> +       case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
>>>>>         default: break;
>>>>>         }
>>>>>
>>>>> @@ -1055,6 +1510,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);
>>>>> diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
>>>>> index 90b7ca1..9a1c30b 100644
>>>>> --- a/include/linux/power/bq27xxx_battery.h
>>>>> +++ b/include/linux/power/bq27xxx_battery.h
>>>>> @@ -66,6 +66,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;
>>>>> --
>>>>> 2.9.3
>>>>>
diff mbox

Patch

diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 8ce2940..f382d18 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -51,7 +51,7 @@ 
 
 #include <linux/power/bq27xxx_battery.h>
 
-#define DRIVER_VERSION		"1.2.0"
+#define DRIVER_VERSION		"1.3.0"
 
 #define BQ27XXX_MANUFACTURER	"Texas Instruments"
 
@@ -59,6 +59,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)
@@ -72,6 +73,11 @@ 
 #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_RS			(20) /* Resistor sense mOhm */
 #define BQ27XXX_POWER_CONSTANT		(29200) /* 29.2 µV^2 * 1000 */
 #define BQ27XXX_CURRENT_CONSTANT	(3570) /* 3.57 µV * 1000 */
@@ -102,6 +108,11 @@  enum bq27xxx_reg_index {
 	BQ27XXX_REG_SOC,	/* State-of-Charge */
 	BQ27XXX_REG_DCAP,	/* Design Capacity */
 	BQ27XXX_REG_AP,		/* Average Power */
+	BQ27XXX_DM_CTRL,	/* BlockDataControl() */
+	BQ27XXX_DM_CLASS,	/* DataClass() */
+	BQ27XXX_DM_BLOCK,	/* DataBlock() */
+	BQ27XXX_DM_DATA,	/* BlockData() */
+	BQ27XXX_DM_CKSUM,	/* BlockDataChecksum() */
 	BQ27XXX_REG_MAX,	/* sentinel */
 };
 
@@ -125,6 +136,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,
@@ -144,6 +160,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,
 	},
 	[BQ27500] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -163,6 +184,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,
 	},
 	[BQ27510] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -182,6 +208,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,
 	},
 	[BQ27530] = {
 		[BQ27XXX_REG_CTRL] = 0x00,
@@ -201,6 +232,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,
@@ -220,6 +256,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,
@@ -239,6 +280,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,
@@ -258,6 +304,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,
 	},
 };
 
@@ -432,6 +483,82 @@  static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_DM_SZ	32
+
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+struct bq27xxx_dm_reg {
+	u8 subclass_id;
+	u8 offset;
+	u8 bytes;
+	u16 min, max;
+};
+
+struct bq27xxx_dm_buf {
+	u8 class;
+	u8 block;
+	u8 a[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->a + 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 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 int poll_interval_param_set(const char *val, const struct kernel_param *kp)
 {
 	struct bq27xxx_device_info *di;
@@ -476,6 +603,316 @@  static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
 	return di->bus.read(di, di->regs[reg_index], single);
 }
 
+static int bq27xxx_battery_set_seal_state(struct bq27xxx_device_info *di,
+					  bool state)
+{
+	int ret;
+
+	if (state) {
+		ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], BQ27XXX_SEALED, false);
+		if (ret < 0)
+			goto out;
+	} else {
+		ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)(di->unseal_key >> 16), false);
+		if (ret < 0)
+			goto out;
+
+		ret = di->bus.write(di, di->regs[BQ27XXX_REG_CTRL], (u16)di->unseal_key, false);
+		if (ret < 0)
+			goto out;
+	}
+	return 0;
+
+out:
+	dev_err(di->dev, "bus error on %s: %d\n", state ? "seal" : "unseal", 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->a[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 = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = di->bus.read_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = di->bus.read(di, di->regs[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,
+					 bool state)
+{
+	int ret, try=100;
+
+	ret = di->bus.write(di, di->regs[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 (100-try) /* remove after other cfgupdate parts are tested */
+		dev_info(di->dev, "cfgupdate %d, retries %d\n", state, 100-try);
+
+	if (try)
+		return 0;
+
+	dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", state);
+	return -EINVAL;
+
+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->updt)
+		return 0;
+
+	if (cfgup) {
+		ret = bq27xxx_battery_set_cfgupdate(di, true);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = di->bus.write(di, di->regs[BQ27XXX_DM_CTRL], 0, true);
+	if (ret < 0)
+		goto out;
+
+	ret = di->bus.write(di, di->regs[BQ27XXX_DM_CLASS], buf->class, true);
+	if (ret < 0)
+		goto out;
+
+	ret = di->bus.write(di, di->regs[BQ27XXX_DM_BLOCK], buf->block, true);
+	if (ret < 0)
+		goto out;
+
+	BQ27XXX_MSLEEP(1);
+
+	ret = di->bus.write_bulk(di, di->regs[BQ27XXX_DM_DATA], buf->a, BQ27XXX_DM_SZ);
+	if (ret < 0)
+		goto out;
+
+	ret = di->bus.write(di, di->regs[BQ27XXX_DM_CKSUM],
+			    bq27xxx_battery_checksum_dm_block(buf), true);
+	if (ret < 0)
+		goto out;
+
+	/* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE!
+	 * If the 'time' delay is insufficient, NVM corruption results on
+	 * the '425 chip (and perhaps others), which could damage the chip.
+	 * It was suggested in this TI tool:
+	 *   http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328
+	 *
+	 * 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...)
+	 * 2. write(BQ27XXX_DM_BLOCK, buf->block)
+	 * 3. sum = read(BQ27XXX_DM_CKSUM)
+	 * 4. if (sum != bq27xxx_battery_checksum_dm_block(buf))
+	 *      report error
+	 */
+
+	if (cfgup) {
+		BQ27XXX_MSLEEP(1);
+		ret = bq27xxx_battery_set_cfgupdate(di, false);
+		if (ret < 0)
+			return ret;
+	} else {
+		/* flash DM updates in <100ms, but reset time isn't documented */
+		BQ27XXX_MSLEEP(400);
+	}
+
+	buf->updt = false;
+	return 0;
+
+out:
+	if (cfgup)
+		bq27xxx_battery_set_cfgupdate(di, false);
+
+	dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
+	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 (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);
+}
+
+void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
+{
+	struct power_supply_battery_info info = {};
+	unsigned int min, max;
+
+	/* functions don't exist for writing data so abort */
+	if (!di->bus.write || !di->bus.write_bulk)
+		return;
+
+	/* no settings to be set for this chipset so abort */
+	if (!di->dm_regs)
+		return;
+
+	if (bq27xxx_battery_set_seal_state(di, false) < 0)
+		return;
+
+	if (power_supply_get_battery_info(di->bat, &info) < 0)
+		goto out;
+
+	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)
+		goto out;
+
+	bq27xxx_battery_set_config(di, &info);
+
+out:
+	bq27xxx_battery_set_seal_state(di, true);
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
@@ -985,6 +1422,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;
@@ -1015,20 +1459,31 @@  static void bq27xxx_external_power_changed(struct power_supply *psy)
 	schedule_delayed_work(&di->work, 0);
 }
 
+#define BQ27XXX_INIT(c,d,k)   \
+	di->chip       = (c); \
+	di->dm_regs    = (d); \
+	di->unseal_key = (k)
+
 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,
+	};
 
 	switch(di->chip) {
-	case BQ27520: di->chip = BQ27510; break;
-	case BQ27531: di->chip = BQ27530; break;
-	case BQ27542: di->chip = BQ27541; break;
-	case BQ27546: di->chip = BQ27541; break;
-	case BQ27742: di->chip = BQ27541; break;
-	case BQ27425: di->chip = BQ27421; break;
-	case BQ27441: di->chip = BQ27421; break;
-	case BQ27621: di->chip = BQ27421; break;
+	case BQ27500: BQ27XXX_INIT(BQ27500, bq27500_dm_regs, 0x04143672); break;
+	case BQ27545: BQ27XXX_INIT(BQ27545, bq27545_dm_regs, 0x04143672); break;
+	case BQ27520: BQ27XXX_INIT(BQ27510, 0, 0); break;
+	case BQ27531: BQ27XXX_INIT(BQ27530, 0, 0); break;
+	case BQ27542: BQ27XXX_INIT(BQ27541, 0, 0); break;
+	case BQ27546: BQ27XXX_INIT(BQ27541, 0, 0); break;
+	case BQ27742: BQ27XXX_INIT(BQ27541, 0, 0); break;
+	case BQ27421: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
+	case BQ27425: BQ27XXX_INIT(BQ27421, bq27425_dm_regs, 0x04143672); break;
+	case BQ27441: BQ27XXX_INIT(BQ27421, bq27421_dm_regs, 0x80008000); break;
+	case BQ27621: BQ27XXX_INIT(BQ27421, bq27621_dm_regs, 0x80008000); break;
 	default: break;
 	}
 
@@ -1055,6 +1510,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);
diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h
index 90b7ca1..9a1c30b 100644
--- a/include/linux/power/bq27xxx_battery.h
+++ b/include/linux/power/bq27xxx_battery.h
@@ -66,6 +66,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;