From: Liam Breck <liam@networkimprov.net>
To: Sebastian Reichel <sre@kernel.org>,
"Andrew F. Davis" <afd@ti.com>,
linux-pm@vger.kernel.org
Cc: Liam Breck <kernel@networkimprov.net>
Subject: Re: [PATCH v13.2 08/11] power: supply: bq27xxx_battery: Add power_supply_battery_info support
Date: Tue, 9 May 2017 03:18:30 -0700 [thread overview]
Message-ID: <CAKvHMgQ6SKsw5Le0CJsRhk99wO80G78C8aKFXgajKk_rgc_uLg@mail.gmail.com> (raw)
In-Reply-To: <20170509082123.27592-3-liam@networkimprov.net>
Hi Sebastian,
This patch only allows update of fuel gauge flash/NVM data if the
kernel is built with CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM=y and a dtb
with monitored-battery config is packaged with the kernel. We expect
some device vendors to field-upgrade the fuel gauge this way.
The patch provides a module param dt_monitored_battery_updates_nvm
(default true) so the user can disable flash/NVM updates if he changes
the battery type. The param has no effect if config option is not =y.
Andrew is concerned that a distro could "by accident" enable the
config option and ship a dtb which causes some smart batteries to be
misconfigured. He believes that the module param should default false
to prevent this. However, that can cause the following scenario:
1) user changes battery type, sets param dt_monitored_battery_updates_nvm=0
2) device vendor ships gauge update in kernel package with
/etc/modprobe.d/xyz containing dt_monitored_battery_updates_nvm=1
3) gauge stops working correctly due to incompatible update
So making the module param default false is a bug.
I think the likelihood of a distro shipping a kernel package with
config option =y and a dtb with monitored-battery config where it does
not belong is vanishingly small. But to prevent a config accident we
could add a second option which also must be =y.
Some more comments inline below...
What do you think?
On Tue, May 9, 2017 at 1:21 AM, Liam Breck <liam@networkimprov.net> wrote:
> From: Liam Breck <kernel@networkimprov.net>
>
> Previously there was no way to configure these chips in the event that the
> defaults didn't match the battery in question.
>
> For chips with RAM data memory (and also those with flash/NVM data memory
> if CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM is defined and the user has not
> set module param dt_monitored_battery_updates_nvm=0) we now call
> power_supply_get_battery_info(), check its values, and write battery
> properties to chip data memory if there is a dm_regs table for the chip.
>
> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
> Signed-off-by: Liam Breck <kernel@networkimprov.net>
> ---
> drivers/power/supply/Kconfig | 11 ++
> drivers/power/supply/bq27xxx_battery.c | 204 ++++++++++++++++++++++++++++++++-
> include/linux/power/bq27xxx_battery.h | 2 +
> 3 files changed, 216 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 76806a0..38fae10 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -178,6 +178,17 @@ config BATTERY_BQ27XXX_I2C
> Say Y here to enable support for batteries with BQ27xxx chips
> connected over an I2C bus.
>
> +config BATTERY_BQ27XXX_DT_UPDATES_NVM
> + bool "BQ27xxx support for update of NVM/flash data memory"
> + depends on BATTERY_BQ27XXX_I2C
> + help
> + Say Y here to enable devicetree monitored-battery config to update
> + NVM/flash data memory. Only enable this option for devices with a
> + fuel gauge mounted on the circuit board, and a battery that cannot
> + easily be replaced with one of a different type. Not for
> + general-purpose kernels, as this can cause misconfiguration of a
> + smart battery with embedded NVM/flash.
Above I have documented why distros should not set this option.
> config BATTERY_DA9030
> tristate "DA9030 battery driver"
> depends on PMIC_DA903X
> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
> index 6a4ac14..ed44439 100644
> --- a/drivers/power/supply/bq27xxx_battery.c
> +++ b/drivers/power/supply/bq27xxx_battery.c
> @@ -805,6 +805,13 @@ static LIST_HEAD(bq27xxx_battery_devices);
>
> #define BQ27XXX_DM_SZ 32
>
> +struct bq27xxx_dm_reg {
> + u8 subclass_id;
> + u8 offset;
> + u8 bytes;
> + u16 min, max;
> +};
> +
> /**
> * struct bq27xxx_dm_buf - chip data memory buffer
> * @class: data memory subclass_id
> @@ -822,6 +829,44 @@ struct bq27xxx_dm_buf {
> bool has_data, dirty;
> };
>
> +#define BQ27XXX_DM_BUF(di, i) { \
> + .class = (di)->dm_regs[i].subclass_id, \
> + .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
> +}
> +
> +static inline u16 *bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
> + struct bq27xxx_dm_reg *reg)
> +{
> + if (buf->class == reg->subclass_id &&
> + buf->block == reg->offset / BQ27XXX_DM_SZ)
> + return (u16 *) (buf->data + reg->offset % BQ27XXX_DM_SZ);
> +
> + return NULL;
> +}
> +
> +enum bq27xxx_dm_reg_id {
> + BQ27XXX_DM_DESIGN_CAPACITY = 0,
> + BQ27XXX_DM_DESIGN_ENERGY,
> + BQ27XXX_DM_TERMINATE_VOLTAGE,
> +};
> +
> +static const char * const bq27xxx_dm_reg_name[] = {
> + [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
> + [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
> + [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
> +};
> +
> +
> +static bool bq27xxx_dt_to_nvm = true;
> +module_param_named(dt_monitored_battery_updates_nvm, bq27xxx_dt_to_nvm, bool, 0444);
> +MODULE_PARM_DESC(dt_monitored_battery_updates_nvm,
> + "Devicetree monitored-battery config updates data memory on NVM/flash chips.\n"
> + "Users must set this =0 when installing a different type of battery!\n"
> + "Default is =1."
> +#ifndef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
> + "\nSetting this affects future kernel updates, not the current configuration."
> +#endif
> +);
The module param has no effect when config option is not set, but I
define the param so the user can see the docs for it with modinfo
either way.
> static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
> {
> @@ -1019,6 +1064,55 @@ static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
> 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->has_data)
> + return;
> +
> + if (be16_to_cpup(prev) == val) {
> + dev_info(di->dev, "%s has %u\n", str, val);
> + return;
> + }
> +
> +#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
> + if (!di->ram_chip && !bq27xxx_dt_to_nvm) {
> +#else
> + if (!di->ram_chip) {
> +#endif
> + /* devicetree and NVM differ; defer to NVM */
> + dev_warn(di->dev, "%s has %u; update to %u disallowed "
> +#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
> + "by dt_monitored_battery_updates_nvm=0"
> +#else
> + "for flash/NVM data memory"
> +#endif
> + "\n", str, be16_to_cpup(prev), val);
> + return;
> + }
The above is how the config option and module param control NVM update.
> + dev_info(di->dev, "update %s to %u\n", str, val);
> +
> + *prev = cpu_to_be16(val);
> + buf->dirty = true;
> +}
> +
> static int bq27xxx_battery_cfgupdate_priv(struct bq27xxx_device_info *di, bool active)
> {
> const int limit = 100;
> @@ -1129,6 +1223,103 @@ static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
> return ret;
> }
>
> +static void bq27xxx_battery_set_config(struct bq27xxx_device_info *di,
> + struct power_supply_battery_info *info)
> +{
> + struct bq27xxx_dm_buf bd = BQ27XXX_DM_BUF(di, BQ27XXX_DM_DESIGN_CAPACITY);
> + struct bq27xxx_dm_buf bt = BQ27XXX_DM_BUF(di, BQ27XXX_DM_TERMINATE_VOLTAGE);
> + bool updated;
> +
> + if (bq27xxx_battery_unseal(di) < 0)
> + return;
> +
> + if (info->charge_full_design_uah != -EINVAL &&
> + info->energy_full_design_uwh != -EINVAL) {
> + bq27xxx_battery_read_dm_block(di, &bd);
> + /* assume design energy & capacity are in same block */
> + bq27xxx_battery_update_dm_block(di, &bd,
> + BQ27XXX_DM_DESIGN_CAPACITY,
> + info->charge_full_design_uah / 1000);
> + bq27xxx_battery_update_dm_block(di, &bd,
> + BQ27XXX_DM_DESIGN_ENERGY,
> + info->energy_full_design_uwh / 1000);
> + }
> +
> + if (info->voltage_min_design_uv != -EINVAL) {
> + bool same = bd.class == bt.class && bd.block == bt.block;
> + if (!same)
> + bq27xxx_battery_read_dm_block(di, &bt);
> + bq27xxx_battery_update_dm_block(di, same ? &bd : &bt,
> + BQ27XXX_DM_TERMINATE_VOLTAGE,
> + info->voltage_min_design_uv / 1000);
> + }
> +
> + updated = bd.dirty || bt.dirty;
> +
> + bq27xxx_battery_write_dm_block(di, &bd);
> + bq27xxx_battery_write_dm_block(di, &bt);
> +
> + bq27xxx_battery_seal(di);
> +
> + if (updated && di->chip != BQ27421) { /* not a cfgupdate chip, so reset */
> + bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_RESET, false);
> + BQ27XXX_MSLEEP(300); /* reset time is not documented */
> + }
> + /* assume bq27xxx_battery_update() is called hereafter */
> +}
> +
> +static void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
> +{
> + struct power_supply_battery_info info = {};
> + unsigned int min, max;
> +
> + if (power_supply_get_battery_info(di->bat, &info) < 0)
> + return;
> +
> + if (!di->dm_regs) {
> + dev_warn(di->dev, "data memory update not supported for chip\n");
> + return;
> + }
> +
> + if (info.energy_full_design_uwh != info.charge_full_design_uah) {
> + if (info.energy_full_design_uwh == -EINVAL)
> + dev_warn(di->dev, "missing battery:energy-full-design-microwatt-hours\n");
> + else if (info.charge_full_design_uah == -EINVAL)
> + dev_warn(di->dev, "missing battery:charge-full-design-microamp-hours\n");
> + }
> +
> + /* assume min == 0 */
> + max = di->dm_regs[BQ27XXX_DM_DESIGN_ENERGY].max;
> + if (info.energy_full_design_uwh > max * 1000) {
> + dev_err(di->dev, "invalid battery:energy-full-design-microwatt-hours %d\n",
> + info.energy_full_design_uwh);
> + info.energy_full_design_uwh = -EINVAL;
> + }
> +
> + /* assume min == 0 */
> + max = di->dm_regs[BQ27XXX_DM_DESIGN_CAPACITY].max;
> + if (info.charge_full_design_uah > max * 1000) {
> + dev_err(di->dev, "invalid battery:charge-full-design-microamp-hours %d\n",
> + info.charge_full_design_uah);
> + info.charge_full_design_uah = -EINVAL;
> + }
> +
> + min = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].min;
> + max = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].max;
> + if ((info.voltage_min_design_uv < min * 1000 ||
> + info.voltage_min_design_uv > max * 1000) &&
> + info.voltage_min_design_uv != -EINVAL) {
> + dev_err(di->dev, "invalid battery:voltage-min-design-microvolt %d\n",
> + info.voltage_min_design_uv);
> + info.voltage_min_design_uv = -EINVAL;
> + }
> +
> + if ((info.energy_full_design_uwh != -EINVAL &&
> + info.charge_full_design_uah != -EINVAL) ||
> + info.voltage_min_design_uv != -EINVAL)
> + bq27xxx_battery_set_config(di, &info);
> +}
> +
> /*
> * Return the battery State-of-Charge
> * Or < 0 if something fails.
> @@ -1646,6 +1837,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;
> @@ -1679,7 +1877,10 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
> int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
> {
> struct power_supply_desc *psy_desc;
> - struct power_supply_config psy_cfg = { .drv_data = di, };
> + struct power_supply_config psy_cfg = {
> + .of_node = di->dev->of_node,
> + .drv_data = di,
> + };
>
> INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
> mutex_init(&di->lock);
> @@ -1704,6 +1905,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 b1defb8..11e1168 100644
> --- a/include/linux/power/bq27xxx_battery.h
> +++ b/include/linux/power/bq27xxx_battery.h
> @@ -63,7 +63,9 @@ struct bq27xxx_device_info {
> struct device *dev;
> int id;
> enum bq27xxx_chip chip;
> + bool ram_chip;
> const char *name;
> + struct bq27xxx_dm_reg *dm_regs;
> u32 unseal_key;
> struct bq27xxx_access_methods bus;
> struct bq27xxx_reg_cache cache;
> --
> 2.9.3
>
next prev parent reply other threads:[~2017-05-09 10:18 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-05-09 8:21 [PATCH v13.2 00/11] bq27xxx_battery partial series Liam Breck
2017-05-09 8:21 ` [PATCH v13.2 07/11] power: supply: bq27xxx_battery: Add chip data memory read/write support Liam Breck
2017-05-09 8:21 ` [PATCH v13.2 08/11] power: supply: bq27xxx_battery: Add power_supply_battery_info support Liam Breck
2017-05-09 10:18 ` Liam Breck [this message]
2017-05-09 8:21 ` [PATCH v13.2 09/11] power: supply: bq27xxx_battery: Enable data memory update for certain chips Liam Breck
2017-05-09 11:09 ` Liam Breck
2017-05-09 8:21 ` [PATCH v13.2 10/11] power: supply: bq27xxx_battery: Flag identical register maps when in debug mode Liam Breck
2017-05-09 11:27 ` Liam Breck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CAKvHMgQ6SKsw5Le0CJsRhk99wO80G78C8aKFXgajKk_rgc_uLg@mail.gmail.com \
--to=liam@networkimprov.net \
--cc=afd@ti.com \
--cc=kernel@networkimprov.net \
--cc=linux-pm@vger.kernel.org \
--cc=sre@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).