All of lore.kernel.org
 help / color / mirror / Atom feed
From: Liam Breck <liam@networkimprov.net>
To: Sebastian Reichel <sre@kernel.org>
Cc: "Andrew F . Davis" <afd@ti.com>,
	linux-pm@vger.kernel.org,
	Matt Ranostay <matt@ranostay.consulting>,
	Liam Breck <kernel@networkimprov.net>
Subject: [PATCH v6 7/8] power: bq27xxx_battery: Add power_supply_battery_info support
Date: Fri, 10 Feb 2017 18:43:39 -0800	[thread overview]
Message-ID: <20170211024340.19491-8-liam@networkimprov.net> (raw)
In-Reply-To: <20170211024340.19491-1-liam@networkimprov.net>

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 non-volatile memory.

Signed-off-by: Matt Ranostay <matt@ranostay.consulting>
Signed-off-by: Liam Breck <kernel@networkimprov.net>
---
 drivers/power/supply/bq27xxx_battery.c | 302 ++++++++++++++++++++++++++++++++-
 1 file changed, 301 insertions(+), 1 deletion(-)

diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 7475a5f..70d0142 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>
@@ -452,6 +453,59 @@ static struct {
 static DEFINE_MUTEX(bq27xxx_list_lock);
 static LIST_HEAD(bq27xxx_battery_devices);
 
+#define BQ27XXX_TERM_V_MIN	2800
+#define BQ27XXX_TERM_V_MAX	3700
+
+#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
+#define BQ27XXX_SUBCLASS_STATE_NVM	82
+
+struct bq27xxx_dm_buf {
+	u8 a[32];
+};
+
+struct bq27xxx_dm_reg {
+	unsigned int subclass_id;
+	unsigned int offset;
+	unsigned int bytes;
+	char *name;
+};
+
+#define BQ27XXX_DM_SUPPORTED(r) ( \
+	   r->subclass_id == BQ27XXX_SUBCLASS_STATE_NVM \
+	&& r->offset <= 30 \
+	&& r->bytes == 2 \
+)
+
+enum bq27xxx_dm_reg_id {
+	BQ27XXX_DM_DESIGN_CAPACITY = 0,
+	BQ27XXX_DM_DESIGN_ENERGY,
+	BQ27XXX_DM_TERMINATE_VOLTAGE,
+	BQ27XXX_DM_END,
+};
+
+static struct bq27xxx_dm_reg bq27425_dm_regs[] = {
+	[BQ27XXX_DM_DESIGN_CAPACITY] =
+		{ BQ27XXX_SUBCLASS_STATE_NVM, 12, 2, "design-capacity" },
+	[BQ27XXX_DM_DESIGN_ENERGY] =
+		{ BQ27XXX_SUBCLASS_STATE_NVM, 14, 2, "design-energy" },
+	[BQ27XXX_DM_TERMINATE_VOLTAGE] =
+		{ BQ27XXX_SUBCLASS_STATE_NVM, 18, 2, "terminate-voltage" },
+};
+
+static struct bq27xxx_dm_reg *bq27xxx_dm_regs[] = {
+	[BQ27425] = bq27425_dm_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;
@@ -496,6 +550,183 @@ 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,
+					 struct bq27xxx_dm_buf *buf)
+{
+	int ret;
+
+	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, buf->a, sizeof buf->a);
+}
+
+static int bq27xxx_battery_print_config(struct bq27xxx_device_info *di)
+{
+	struct bq27xxx_dm_buf buf;
+	struct bq27xxx_dm_reg *reg = bq27xxx_dm_regs[di->chip];
+	int ret, i;
+
+	ret = bq27xxx_battery_read_dm_block(di, BQ27XXX_SUBCLASS_STATE_NVM,
+					    &buf);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < BQ27XXX_DM_END; i++, reg++) {
+		int val;
+
+		if (!BQ27XXX_DM_SUPPORTED(reg)) {
+			dev_warn(di->dev, "unsupported config register %s\n", reg->name);
+			continue;
+		}
+
+		val = be16_to_cpup((u16 *) &buf.a[reg->offset]);
+
+		dev_info(di->dev, "config register %s set at %d\n", reg->name, val);
+	}
+
+	return 0;
+}
+
+static bool 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 = &bq27xxx_dm_regs[di->chip][reg_id];
+	u16 *prev;
+
+	if (!BQ27XXX_DM_SUPPORTED(reg))
+		return false;
+
+	prev = (u16 *) &buf->a[reg->offset];
+
+	if (be16_to_cpup(prev) == val)
+		return false;
+
+	*prev = cpu_to_be16(val);
+
+	return true;
+}
+
+static u8 bq27xxx_battery_checksum(struct bq27xxx_dm_buf *buf)
+{
+	u16 sum = 0;
+	int i;
+
+	for (i = 0; i < sizeof buf->a; i++) {
+		sum += buf->a[i];
+		sum &= 0xff;
+	}
+
+	return 0xff - sum;
+}
+
+static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
+					  int subclass,
+					  struct bq27xxx_dm_buf *buf)
+{
+	int ret;
+
+	ret = di->bus.write(di, BQ27XXX_REG_CTRL, BQ27XXX_SET_CFGUPDATE, 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;
+
+	ret = di->bus.write_bulk(di, BQ27XXX_BLOCK_DATA, buf->a, sizeof buf->a);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(1000, 1500);
+
+	ret = di->bus.write(di, BQ27XXX_BLOCK_DATA_CHECKSUM,
+			    bq27xxx_battery_checksum(buf), true);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(1000, 1500);
+
+	return di->bus.write(di, BQ27XXX_REG_CTRL, BQ27XXX_SOFT_RESET, false);
+}
+
+static int bq27xxx_battery_set_config(struct bq27xxx_device_info *di,
+				      struct power_supply_battery_info *info)
+{
+	struct bq27xxx_dm_buf buf;
+	int ret;
+
+	ret = bq27xxx_battery_read_dm_block(di, BQ27XXX_SUBCLASS_STATE_NVM,
+					    &buf);
+	if (ret < 0)
+		return ret;
+
+	if (info->charge_full_design_uah != -EINVAL
+	 && info->energy_full_design_uwh != -EINVAL) {
+		ret |= bq27xxx_battery_update_dm_block(di, &buf,
+					BQ27XXX_DM_DESIGN_CAPACITY,
+					info->charge_full_design_uah / 1000);
+		ret |= bq27xxx_battery_update_dm_block(di, &buf,
+					BQ27XXX_DM_DESIGN_ENERGY,
+					info->energy_full_design_uwh / 1000);
+	}
+
+	if (info->voltage_min_design_uv != -EINVAL)
+		ret |= bq27xxx_battery_update_dm_block(di, &buf,
+					BQ27XXX_DM_TERMINATE_VOLTAGE,
+					info->voltage_min_design_uv / 1000);
+
+	if (ret) {
+		dev_info(di->dev, "updating NVM settings\n");
+		return bq27xxx_battery_write_dm_block(di, BQ27XXX_SUBCLASS_STATE_NVM,
+						      &buf);
+	}
+
+	return 0;
+}
+
 /*
  * Return the battery State-of-Charge
  * Or < 0 if something fails.
@@ -757,6 +988,64 @@ 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_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_TERM_V_MIN * 1000
+	  || info.voltage_min_design_uv > BQ27XXX_TERM_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.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_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, };
@@ -1006,6 +1295,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;
@@ -1039,7 +1335,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);
@@ -1064,6 +1363,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);
-- 
2.9.3


  parent reply	other threads:[~2017-02-11  2:44 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-02-11  2:43 [PATCH v6 0/8] devicetree battery support and client bq27xxx_battery Liam Breck
2017-02-11  2:43 ` [PATCH v6 1/8] devicetree: power: Add battery.txt Liam Breck
     [not found]   ` <20170211024340.19491-2-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org>
2017-02-13 16:06     ` Andrew F. Davis
     [not found]       ` <ce69f358-ac3b-993d-0639-844856362c9d-l0cyMroinI0@public.gmane.org>
2017-02-13 20:58         ` Liam Breck
     [not found]           ` <CAKvHMgSSpbkX1NMwaDmuJwBcgu6Avy65izAaBGTYongqL51kWQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2017-02-13 21:12             ` Andrew F. Davis
2017-02-13 21:31               ` Liam Breck
2017-02-14 20:22                 ` Andrew F. Davis
2017-02-14 22:38                   ` Liam Breck
2017-02-15 15:54                     ` Andrew F. Davis
2017-02-15 20:05                       ` Liam Breck
2017-02-15 23:21                         ` Andrew F. Davis
2017-02-16  0:56                           ` Liam Breck
2017-02-17 20:12                             ` Andrew F. Davis
2017-02-18  4:10                               ` Liam Breck
2017-02-20 16:47                                 ` Andrew F. Davis
     [not found] ` <20170211024340.19491-1-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org>
2017-02-11  2:43   ` [PATCH v6 2/8] devicetree: property-units: Add uWh and uAh units Liam Breck
2017-02-11  2:43     ` Liam Breck
2017-02-11  2:43 ` [PATCH v6 3/8] devicetree: power: bq27xxx: Add monitored-battery documentation Liam Breck
2017-02-11  2:43 ` [PATCH v6 4/8] power: power_supply: Add power_supply_battery_info and API Liam Breck
2017-02-11  2:43 ` [PATCH v6 5/8] power: bq27xxx_battery: Define access methods to write chip registers Liam Breck
2017-02-11  2:43 ` [PATCH v6 6/8] power: bq27xxx_battery: Add BQ27425 chip id Liam Breck
2017-02-11  2:43 ` Liam Breck [this message]
2017-02-13  2:32   ` [PATCH v6 7/8] power: bq27xxx_battery: Add power_supply_battery_info support Liam Breck
2017-02-15 19:45     ` Liam Breck
2017-02-17 19:56       ` Liam Breck
2017-02-11  2:43 ` [PATCH v6 8/8] power: bq27xxx_battery_i2c: Add I2C bulk read/write functions 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=20170211024340.19491-8-liam@networkimprov.net \
    --to=liam@networkimprov.net \
    --cc=afd@ti.com \
    --cc=kernel@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.