From: "Andrew F. Davis" <afd@ti.com>
To: Liam Breck <liam@networkimprov.net>, Sebastian Reichel <sre@kernel.org>
Cc: Matt Ranostay <matt@ranostay.consulting>,
linux-pm@vger.kernel.org, Liam Breck <kernel@networkimprov.net>
Subject: Re: [PATCH v5 5/8] power: bq27xxx_battery: Add power_supply_battery_info support
Date: Mon, 6 Feb 2017 17:29:53 -0600 [thread overview]
Message-ID: <dde60a3f-5c99-e39b-8e91-68f899f4be89@ti.com> (raw)
In-Reply-To: <20170204091603.32242-6-liam@networkimprov.net>
On 02/04/2017 03:16 AM, Liam Breck wrote:
> From: Matt Ranostay <matt@ranostay.consulting>
>
> From: Matt Ranostay <matt@ranostay.consulting>
>
> Previously there was no way to set 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 NVRAM.
>
I haven't had much time to review this, but have you seen our old
internal version of this driver?:
http://git.ti.com/bms-linux/bms-kernel/blobs/master/drivers/power/bq27x00_battery.c
I would like that most of the NVRAM checks and sets are kept in userspace:
http://git.ti.com/bms-linux/bqtool/trees/master
In kernel don't really have a good way to know when the battery has been
swapped and the DT info might not be valid for this new battery. I guess
there is really no good way, but I imagine this is why we never
upstreamed the rest of this driver before.
Andrew
> Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
> Signed-off-by: Liam Breck <kernel@networkimprov.net>
> ---
> drivers/power/supply/bq27xxx_battery.c | 303 ++++++++++++++++++++++++++++++++-
> include/linux/power/bq27xxx_battery.h | 1 +
> 2 files changed, 303 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
> index 7272d1e..54ef18a 100644
> --- a/drivers/power/supply/bq27xxx_battery.c
> +++ b/drivers/power/supply/bq27xxx_battery.c
> @@ -37,6 +37,7 @@
> * http://www.ti.com/product/bq27621-g1
> */
>
> +#include <linux/delay.h>
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
> @@ -432,6 +433,54 @@ static struct {
> static DEFINE_MUTEX(bq27xxx_list_lock);
> static LIST_HEAD(bq27xxx_battery_devices);
>
> +#define BQ27XXX_MIN_V_MIN 2800
> +#define BQ27XXX_MIN_V_MAX 3700
> +#define BQ27XXX_MAX_V_MIN 0
> +#define BQ27XXX_MAX_V_MAX 5000
> +
> +#define BQ27XXX_BLOCK_DATA_CLASS 0x3E
> +#define BQ27XXX_DATA_BLOCK 0x3F
> +#define BQ27XXX_BLOCK_DATA 0x40
> +#define BQ27XXX_BLOCK_DATA_CHECKSUM 0x60
> +#define BQ27XXX_BLOCK_DATA_CONTROL 0x61
> +#define BQ27XXX_SET_CFGUPDATE 0x13
> +#define BQ27XXX_SOFT_RESET 0x42
> +
> +enum bq27xxx_dm_subclass_index {
> + BQ27XXX_DM_DESIGN_CAPACITY = 0,
> + BQ27XXX_DM_DESIGN_ENERGY,
> + BQ27XXX_DM_TERMINATE_VOLTAGE,
> + BQ27XXX_DM_V_AT_CHARGE_TERM,
> + BQ27XXX_DM_END,
> +};
> +
> +struct bq27xxx_dm_regs {
> + unsigned int subclass_id;
> + unsigned int offset;
> + char *name;
> +};
> +
> +#define BQ27XXX_GAS_GAUGING_STATE_SUBCLASS 82
> +
> +static struct bq27xxx_dm_regs bq27425_dm_subclass_regs[] = {
> + [BQ27XXX_DM_DESIGN_CAPACITY] =
> + { BQ27XXX_GAS_GAUGING_STATE_SUBCLASS, 12, "design-capacity" },
> + [BQ27XXX_DM_DESIGN_ENERGY] =
> + { BQ27XXX_GAS_GAUGING_STATE_SUBCLASS, 14, "design-energy" },
> + [BQ27XXX_DM_TERMINATE_VOLTAGE] =
> + { BQ27XXX_GAS_GAUGING_STATE_SUBCLASS, 18, "terminate-voltage" },
> + [BQ27XXX_DM_V_AT_CHARGE_TERM] =
> + { BQ27XXX_GAS_GAUGING_STATE_SUBCLASS, 36, "v-at-charge-termination" },
> +};
> +
> +static struct bq27xxx_dm_regs *bq27xxx_dm_subclass_regs[] = {
> + [BQ27425] = bq27425_dm_subclass_regs,
> +};
> +
> +static unsigned int bq27xxx_unseal_keys[] = {
> + [BQ27425] = 0x04143672,
> +};
> +
> static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
> {
> struct bq27xxx_device_info *di;
> @@ -476,6 +525,179 @@ 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)
> +{
> + unsigned int key = bq27xxx_unseal_keys[di->chip];
> + int ret;
> +
> + if (state)
> + return di->bus.write(di, BQ27XXX_REG_CTRL, 0x20, false);
> +
> + ret = di->bus.write(di, BQ27XXX_REG_CTRL, (key >> 16) & 0xffff, false);
> + if (ret < 0)
> + return ret;
> +
> + return di->bus.write(di, BQ27XXX_REG_CTRL, key & 0xffff, false);
> +}
> +
> +static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
> + int subclass)
> +{
> + int ret = di->bus.write(di, BQ27XXX_REG_CTRL, 0, false);
> +
> + if (ret < 0)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_BLOCK_DATA_CONTROL, 0, true);
> + if (ret < 0)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_BLOCK_DATA_CLASS, subclass, true);
> + if (ret < 0)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_DATA_BLOCK, 0, true);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(1000, 1500);
> +
> + return di->bus.read_bulk(di, BQ27XXX_BLOCK_DATA,
> + (u8 *) &di->buffer, sizeof(di->buffer));
> +}
> +
> +static int bq27xxx_battery_print_config(struct bq27xxx_device_info *di)
> +{
> + struct bq27xxx_dm_regs *reg = bq27xxx_dm_subclass_regs[di->chip];
> + int ret, i;
> +
> + ret = bq27xxx_battery_read_dm_block(di,
> + BQ27XXX_GAS_GAUGING_STATE_SUBCLASS);
> + if (ret < 0)
> + return ret;
> +
> + for (i = 0; i < BQ27XXX_DM_END; i++) {
> + int val;
> +
> + if (reg->subclass_id != BQ27XXX_GAS_GAUGING_STATE_SUBCLASS)
> + continue;
> +
> + val = be16_to_cpup((u16 *) &di->buffer[reg->offset]);
> +
> + dev_info(di->dev, "settings for %s set at %d\n", reg->name, val);
> +
> + reg++;
> + }
> +
> + return 0;
> +}
> +
> +static bool bq27xxx_battery_update_dm_setting(struct bq27xxx_device_info *di,
> + unsigned int reg, unsigned int val)
> +{
> + struct bq27xxx_dm_regs *dm_reg = &bq27xxx_dm_subclass_regs[di->chip][reg];
> + u16 *prev = (u16 *) &di->buffer[dm_reg->offset];
> +
> + if (be16_to_cpup(prev) == val)
> + return false;
> +
> + *prev = cpu_to_be16(val);
> +
> + return true;
> +}
> +
> +static u8 bq27xxx_battery_checksum(struct bq27xxx_device_info *di)
> +{
> + u8 *data = (u8 *) &di->buffer;
> + u16 sum = 0;
> + int i;
> +
> + for (i = 0; i < sizeof(di->buffer); i++) {
> + sum += data[i];
> + sum &= 0xff;
> + }
> +
> + return 0xff - sum;
> +}
> +
> +static int bq27xxx_battery_write_nvram(struct bq27xxx_device_info *di,
> + unsigned int subclass)
> +{
> + int ret;
> +
> + ret = di->bus.write(di, BQ27XXX_REG_CTRL, BQ27XXX_SET_CFGUPDATE, false);
> + if (ret)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_BLOCK_DATA_CONTROL, 0, true);
> + if (ret)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_BLOCK_DATA_CLASS, subclass, true);
> + if (ret)
> + return ret;
> +
> + ret = di->bus.write(di, BQ27XXX_DATA_BLOCK, 0, true);
> + if (ret)
> + return ret;
> +
> + ret = di->bus.write_bulk(di, BQ27XXX_BLOCK_DATA,
> + (u8 *) &di->buffer, sizeof(di->buffer));
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(1000, 1500);
> +
> + di->bus.write(di, BQ27XXX_BLOCK_DATA_CHECKSUM,
> + bq27xxx_battery_checksum(di), true);
> +
> + usleep_range(1000, 1500);
> +
> + di->bus.write(di, BQ27XXX_REG_CTRL, BQ27XXX_SOFT_RESET, false);
> +
> + return 0;
> +}
> +
> +static int bq27xxx_battery_set_config(struct bq27xxx_device_info *di,
> + struct power_supply_battery_info *info)
> +{
> + int ret;
> +
> + ret = bq27xxx_battery_read_dm_block(di,
> + BQ27XXX_GAS_GAUGING_STATE_SUBCLASS);
> + if (ret < 0)
> + return ret;
> +
> + if (info->charge_full_design_uah != -EINVAL
> + && info->energy_full_design_uwh != -EINVAL) {
> + ret |= bq27xxx_battery_update_dm_setting(di,
> + BQ27XXX_DM_DESIGN_CAPACITY,
> + info->charge_full_design_uah / 1000);
> + ret |= bq27xxx_battery_update_dm_setting(di,
> + BQ27XXX_DM_DESIGN_ENERGY,
> + info->energy_full_design_uwh / 1000);
> + }
> +
> + if (info->voltage_min_design_uv != -EINVAL)
> + ret |= bq27xxx_battery_update_dm_setting(di,
> + BQ27XXX_DM_TERMINATE_VOLTAGE,
> + info->voltage_min_design_uv / 1000);
> +
> + if (info->voltage_max_design_uv != -EINVAL)
> + ret |= bq27xxx_battery_update_dm_setting(di,
> + BQ27XXX_DM_V_AT_CHARGE_TERM,
> + info->voltage_max_design_uv / 1000);
> +
> + if (ret) {
> + dev_info(di->dev, "updating NVM settings\n");
> + return bq27xxx_battery_write_nvram(di,
> + BQ27XXX_GAS_GAUGING_STATE_SUBCLASS);
> + }
> +
> + return 0;
> +}
> +
> /*
> * Return the battery State-of-Charge
> * Or < 0 if something fails.
> @@ -736,6 +958,73 @@ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di)
> return POWER_SUPPLY_HEALTH_GOOD;
> }
>
> +void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
> +{
> + struct power_supply_battery_info info = {};
> +
> + /* 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 (!bq27xxx_dm_subclass_regs[di->chip])
> + return;
> +
> + bq27xxx_battery_set_seal_state(di, false);
> +
> + 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");
> + }
> +
> + if (info.energy_full_design_uwh > 0x7fff * 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;
> + }
> +
> + if (info.charge_full_design_uah > 0x7fff * 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;
> + }
> +
> + if ((info.voltage_min_design_uv < BQ27XXX_MIN_V_MIN * 1000
> + || info.voltage_min_design_uv > BQ27XXX_MIN_V_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.voltage_max_design_uv < BQ27XXX_MAX_V_MIN * 1000
> + || info.voltage_max_design_uv > BQ27XXX_MAX_V_MAX * 1000)
> + && info.voltage_max_design_uv != -EINVAL) {
> + dev_err(di->dev, "invalid battery:voltage-max-design-microvolt %d\n",
> + info.voltage_max_design_uv);
> + info.voltage_max_design_uv = -EINVAL;
> + }
> +
> + if ((info.energy_full_design_uwh == -EINVAL
> + || info.charge_full_design_uah == -EINVAL)
> + && info.voltage_min_design_uv == -EINVAL
> + && info.voltage_max_design_uv == -EINVAL)
> + goto out;
> +
> + bq27xxx_battery_set_config(di, &info);
> +
> +out:
> + bq27xxx_battery_print_config(di);
> + bq27xxx_battery_set_seal_state(di, true);
> +}
> +
> void bq27xxx_battery_update(struct bq27xxx_device_info *di)
> {
> struct bq27xxx_reg_cache cache = {0, };
> @@ -985,6 +1274,14 @@ 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:
> + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> + return -EINVAL;
> case POWER_SUPPLY_PROP_CYCLE_COUNT:
> ret = bq27xxx_simple_value(di->cache.cycle_count, val);
> break;
> @@ -1018,7 +1315,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);
> @@ -1043,6 +1343,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 bed9557..c433815 100644
> --- a/include/linux/power/bq27xxx_battery.h
> +++ b/include/linux/power/bq27xxx_battery.h
> @@ -62,6 +62,7 @@ struct bq27xxx_device_info {
> struct list_head list;
> struct mutex lock;
> u8 *regs;
> + u8 buffer[32];
> };
>
> void bq27xxx_battery_update(struct bq27xxx_device_info *di);
>
next prev parent reply other threads:[~2017-02-06 23:30 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-02-04 9:15 [PATCH v5 0/8] Devicetree battery support and client bq27xxx_battery Liam Breck
2017-02-04 9:15 ` [PATCH v5 1/8] devicetree: power: Add battery.txt Liam Breck
[not found] ` <20170204091603.32242-2-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org>
2017-02-04 10:03 ` Matt Ranostay
2017-02-08 21:58 ` Rob Herring
2017-02-11 2:03 ` Liam Breck
[not found] ` <20170204091603.32242-1-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org>
2017-02-04 9:15 ` [PATCH v5 2/8] devicetree: property-units: Add uWh and uAh units Liam Breck
2017-02-04 9:15 ` [PATCH v5 3/8] devicetree: power: bq27xxx: Add monitored-battery documentation Liam Breck
[not found] ` <20170204091603.32242-4-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org>
2017-02-08 22:00 ` Rob Herring
2017-02-04 9:15 ` [PATCH v5 4/8] power: power_supply: Add power_supply_battery_info and API Liam Breck
2017-02-04 9:16 ` [PATCH v5 5/8] power: bq27xxx_battery: Add power_supply_battery_info support Liam Breck
2017-02-04 9:42 ` kbuild test robot
2017-02-06 23:29 ` Andrew F. Davis [this message]
2017-02-07 0:15 ` Liam Breck
2017-02-04 9:16 ` [PATCH v5 6/8] power: bq27xxx_battery_i2c: Add I2C bulk read/write functions Liam Breck
2017-02-04 9:16 ` [PATCH v5 7/8] power: bq27xxx_battery: Define access methods to write chip registers Liam Breck
2017-02-04 9:35 ` Liam Breck
2017-02-06 23:32 ` Andrew F. Davis
2017-02-04 9:16 ` [PATCH v5 8/8] power: bq27xxx_battery: Add BQ27425 chip id Liam Breck
2017-02-04 9:32 ` 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=dde60a3f-5c99-e39b-8e91-68f899f4be89@ti.com \
--to=afd@ti.com \
--cc=kernel@networkimprov.net \
--cc=liam@networkimprov.net \
--cc=linux-pm@vger.kernel.org \
--cc=matt@ranostay.consulting \
--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