summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-07-04 14:25:14 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2017-07-04 14:25:14 -0700
commita897a10141ae4deb43346fbf2ae2a5149bd7fd5a (patch)
tree14dce2d8e155ca50e19c3ac5e3563fe55ca0cc67 /drivers
parenteceeae414edccb07fad683011c0c65abe8a3a554 (diff)
parentbfa953d336cdd713f6968c85ca820ef22333dc35 (diff)
Merge tag 'for-v4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel: "New drivers: - Linear ltc3651 charger driver - Motorola CPCAP battery fuel-gauge driver New chip/feature support: - bq27xxx: prepare for chip data setup - axp20x_battery: support max charge current setup New core features: - add Apple Brick ID type - support "supplied-from" device property for generic ACPI/pdata support - support strings for sysfs properties representing enums - introduce battery-info (backend is DT only for now) - provide reboot-mode header globally .. and misc fixes" * tag 'for-v4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (39 commits) power: supply: sbs-battery: Don't needlessly set CAPACITY_MODE power: supply: sbs-battery: Prevent CAPACITY_MODE races power: supply: bq24735: remove incorrect le16_to_cpu calls power: supply: sbs-battery: remove incorrect le16_to_cpu calls power: supply: cpcap-charger: Add missing power_supply_config power: supply: twl4030-charger: move allocation of iio channel to the beginning power: supply: twl4030-charger: allocate iio by devm_iio_channel_get() and fix error path power: supply: core: constify psy_tcd_ops. dt-bindings: power: supply: cpcap-battery: Add power-supplies property dt-bindings: power: supply: move max8903-charger.txt to proper location dt-bindings: power: supply: move maxim,max14656.txt to proper location power: supply: twl4030_charger: Use sysfs_match_string() helper power: reset: reboot-mode: Make include file global power: supply: axp20x_battery: add DT support for battery max constant charge current power: supply: axp20x_battery: add support for DT battery power: supply: bq27xxx: Add power_supply_battery_info support power: supply: bq27xxx: Add chip data memory read/write support power: supply: bq27xxx: Add bulk transfer bus methods dt-bindings: power: supply: bq27xxx: Add monitored-battery documentation power: supply: core: Add power_supply_prop_precharge ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/power/reset/Kconfig4
-rw-r--r--drivers/power/reset/at91-poweroff.c2
-rw-r--r--drivers/power/reset/at91-sama5d2_shdwc.c2
-rw-r--r--drivers/power/reset/reboot-mode.c2
-rw-r--r--drivers/power/reset/reboot-mode.h18
-rw-r--r--drivers/power/reset/syscon-reboot-mode.c2
-rw-r--r--drivers/power/supply/Kconfig26
-rw-r--r--drivers/power/supply/Makefile2
-rw-r--r--drivers/power/supply/axp20x_battery.c88
-rw-r--r--drivers/power/supply/axp20x_usb_power.c2
-rw-r--r--drivers/power/supply/bq24735-charger.c6
-rw-r--r--drivers/power/supply/bq27xxx_battery.c536
-rw-r--r--drivers/power/supply/bq27xxx_battery_i2c.c82
-rw-r--r--drivers/power/supply/cpcap-battery.c808
-rw-r--r--drivers/power/supply/cpcap-charger.c89
-rw-r--r--drivers/power/supply/ltc3651-charger.c210
-rw-r--r--drivers/power/supply/power_supply_core.c83
-rw-r--r--drivers/power/supply/power_supply_sysfs.c125
-rw-r--r--drivers/power/supply/sbs-battery.c29
-rw-r--r--drivers/power/supply/twl4030_charger.c97
20 files changed, 2000 insertions, 213 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 13f1714cf6f7..ca0de1a78e85 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -58,9 +58,9 @@ config POWER_RESET_BRCMKONA
config POWER_RESET_BRCMSTB
bool "Broadcom STB reset driver"
- depends on ARM || MIPS || COMPILE_TEST
+ depends on ARM || ARM64 || MIPS || COMPILE_TEST
depends on MFD_SYSCON
- default ARCH_BRCMSTB
+ default ARCH_BRCMSTB || BMIPS_GENERIC
help
This driver provides restart support for Broadcom STB boards.
diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c
index c6c3beea72f9..c30c40193aaa 100644
--- a/drivers/power/reset/at91-poweroff.c
+++ b/drivers/power/reset/at91-poweroff.c
@@ -97,7 +97,7 @@ static void at91_lpddr_poweroff(void)
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
- : "r0");
+ : "r6");
}
static int at91_poweroff_get_wakeup_mode(struct device_node *np)
diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c
index 90b0b5a70ce5..55fce8b75245 100644
--- a/drivers/power/reset/at91-sama5d2_shdwc.c
+++ b/drivers/power/reset/at91-sama5d2_shdwc.c
@@ -132,7 +132,7 @@ static void at91_lpddr_poweroff(void)
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc->at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
- : "r0");
+ : "r6");
}
static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
index fb512183ace3..8f975ca0a8c4 100644
--- a/drivers/power/reset/reboot-mode.c
+++ b/drivers/power/reset/reboot-mode.c
@@ -13,7 +13,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/reboot.h>
-#include "reboot-mode.h"
+#include <linux/reboot-mode.h>
#define PREFIX "mode-"
diff --git a/drivers/power/reset/reboot-mode.h b/drivers/power/reset/reboot-mode.h
deleted file mode 100644
index 75f7fe5c881f..000000000000
--- a/drivers/power/reset/reboot-mode.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __REBOOT_MODE_H__
-#define __REBOOT_MODE_H__
-
-struct reboot_mode_driver {
- struct device *dev;
- struct list_head head;
- int (*write)(struct reboot_mode_driver *reboot, unsigned int magic);
- struct notifier_block reboot_notifier;
-};
-
-int reboot_mode_register(struct reboot_mode_driver *reboot);
-int reboot_mode_unregister(struct reboot_mode_driver *reboot);
-int devm_reboot_mode_register(struct device *dev,
- struct reboot_mode_driver *reboot);
-void devm_reboot_mode_unregister(struct device *dev,
- struct reboot_mode_driver *reboot);
-
-#endif
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
index c8c371b285b1..563a97d7f73e 100644
--- a/drivers/power/reset/syscon-reboot-mode.c
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -15,7 +15,7 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
-#include "reboot-mode.h"
+#include <linux/reboot-mode.h>
struct syscon_reboot_mode {
struct regmap *map;
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 86f40bf37c34..969f5005669c 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -82,6 +82,14 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
+config BATTERY_CPCAP
+ tristate "Motorola CPCAP PMIC battery driver"
+ depends on MFD_CPCAP && IIO
+ default MFD_CPCAP
+ help
+ Say Y here to enable support for battery on Motorola
+ phones and tablets such as droid 4.
+
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
depends on W1 && W1_SLAVE_DS2760
@@ -190,6 +198,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.
+
config BATTERY_DA9030
tristate "DA9030 battery driver"
depends on PMIC_DA903X
@@ -408,6 +427,13 @@ config CHARGER_MANAGER
runtime and in suspend-to-RAM by waking up the system periodically
with help of suspend_again support.
+config CHARGER_LTC3651
+ tristate "LTC3651 charger"
+ depends on GPIOLIB
+ help
+ Say Y to include support for the LTC3651 battery charger which reports
+ its status via GPIO lines.
+
config CHARGER_MAX14577
tristate "Maxim MAX14577/77836 battery charger driver"
depends on MFD_MAX14577
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index a39126d7a6ce..a41f40957847 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
+obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
@@ -61,6 +62,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
index 5d29b2eab8fc..7494f0f0eadb 100644
--- a/drivers/power/supply/axp20x_battery.c
+++ b/drivers/power/supply/axp20x_battery.c
@@ -60,6 +60,8 @@ struct axp20x_batt_ps {
struct iio_channel *batt_chrg_i;
struct iio_channel *batt_dischrg_i;
struct iio_channel *batt_v;
+ /* Maximum constant charge current */
+ unsigned int max_ccc;
u8 axp_id;
};
@@ -129,6 +131,14 @@ static void raw_to_constant_charge_current(struct axp20x_batt_ps *axp, int *val)
*val = *val * 150000 + 300000;
}
+static void constant_charge_current_to_raw(struct axp20x_batt_ps *axp, int *val)
+{
+ if (axp->axp_id == AXP209_ID)
+ *val = (*val - 300000) / 100000;
+ else
+ *val = (*val - 300000) / 150000;
+}
+
static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp,
int *val)
{
@@ -221,9 +231,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
- val->intval = AXP20X_CHRG_CTRL1_TGT_CURR;
- raw_to_constant_charge_current(axp20x_batt, &val->intval);
-
+ val->intval = axp20x_batt->max_ccc;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
@@ -340,10 +348,10 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt,
static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
int charge_current)
{
- if (axp_batt->axp_id == AXP209_ID)
- charge_current = (charge_current - 300000) / 100000;
- else
- charge_current = (charge_current - 300000) / 150000;
+ if (charge_current > axp_batt->max_ccc)
+ return -EINVAL;
+
+ constant_charge_current_to_raw(axp_batt, &charge_current);
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
return -EINVAL;
@@ -352,6 +360,36 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
AXP20X_CHRG_CTRL1_TGT_CURR, charge_current);
}
+static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp,
+ int charge_current)
+{
+ bool lower_max = false;
+
+ constant_charge_current_to_raw(axp, &charge_current);
+
+ if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
+ return -EINVAL;
+
+ raw_to_constant_charge_current(axp, &charge_current);
+
+ if (charge_current > axp->max_ccc)
+ dev_warn(axp->dev,
+ "Setting max constant charge current higher than previously defined. Note that increasing the constant charge current may damage your battery.\n");
+ else
+ lower_max = true;
+
+ axp->max_ccc = charge_current;
+
+ if (lower_max) {
+ int current_cc;
+
+ axp20x_get_constant_charge_current(axp, &current_cc);
+ if (current_cc > charge_current)
+ axp20x_set_constant_charge_current(axp, charge_current);
+ }
+
+ return 0;
+}
static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt,
int min_voltage)
{
@@ -380,6 +418,9 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return axp20x_set_constant_charge_current(axp20x_batt,
val->intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return axp20x_set_max_constant_charge_current(axp20x_batt,
+ val->intval);
default:
return -EINVAL;
@@ -405,7 +446,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
{
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
- psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT;
+ psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
+ psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
}
static const struct power_supply_desc axp20x_batt_ps_desc = {
@@ -433,6 +475,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
{
struct axp20x_batt_ps *axp20x_batt;
struct power_supply_config psy_cfg = {};
+ struct power_supply_battery_info info;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
@@ -484,6 +527,35 @@ static int axp20x_power_probe(struct platform_device *pdev)
return PTR_ERR(axp20x_batt->batt);
}
+ if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
+ int vmin = info.voltage_min_design_uv;
+ int ccc = info.constant_charge_current_max_ua;
+
+ if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
+ vmin))
+ dev_err(&pdev->dev,
+ "couldn't set voltage_min_design\n");
+
+ /* Set max to unverified value to be able to set CCC */
+ axp20x_batt->max_ccc = ccc;
+
+ if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt,
+ ccc)) {
+ dev_err(&pdev->dev,
+ "couldn't set constant charge current from DT: fallback to minimum value\n");
+ ccc = 300000;
+ axp20x_batt->max_ccc = ccc;
+ axp20x_set_constant_charge_current(axp20x_batt, ccc);
+ }
+ }
+
+ /*
+ * Update max CCC to a valid value if battery info is present or set it
+ * to current register value by default.
+ */
+ axp20x_get_constant_charge_current(axp20x_batt,
+ &axp20x_batt->max_ccc);
+
return 0;
}
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index 2397c482656e..44f70dcea61e 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -339,7 +339,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
"VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
static const char * const axp22x_irq_names[] = {
"VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
- static const char * const *irq_names;
+ const char * const *irq_names;
const struct power_supply_desc *usb_power_desc;
int i, irq, ret;
diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c
index eb0145380def..6931e1d826f5 100644
--- a/drivers/power/supply/bq24735-charger.c
+++ b/drivers/power/supply/bq24735-charger.c
@@ -81,14 +81,12 @@ static int bq24735_charger_property_is_writeable(struct power_supply *psy,
static inline int bq24735_write_word(struct i2c_client *client, u8 reg,
u16 value)
{
- return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value));
+ return i2c_smbus_write_word_data(client, reg, value);
}
static inline int bq24735_read_word(struct i2c_client *client, u8 reg)
{
- s32 ret = i2c_smbus_read_word_data(client, reg);
-
- return ret < 0 ? ret : le16_to_cpu(ret);
+ return i2c_smbus_read_word_data(client, reg);
}
static int bq24735_update_word(struct i2c_client *client, u8 reg,
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index 398801a21b86..ed44439d0112 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -5,6 +5,7 @@
* Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
* Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de>
* Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com>
+ * Copyright (C) 2017 Liam Breck <kernel@networkimprov.net>
*
* Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
*
@@ -65,6 +66,7 @@
#define BQ27XXX_FLAG_DSC BIT(0)
#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */
#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_CFGUP BIT(4)
#define BQ27XXX_FLAG_FC BIT(9)
#define BQ27XXX_FLAG_OTD BIT(14)
#define BQ27XXX_FLAG_OTC BIT(15)
@@ -78,6 +80,12 @@
#define BQ27000_FLAG_FC BIT(5)
#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */
+/* control register params */
+#define BQ27XXX_SEALED 0x20
+#define BQ27XXX_SET_CFGUPDATE 0x13
+#define BQ27XXX_SOFT_RESET 0x42
+#define BQ27XXX_RESET 0x41
+
#define BQ27XXX_RS (20) /* Resistor sense mOhm */
#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */
#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */
@@ -108,9 +116,21 @@ enum bq27xxx_reg_index {
BQ27XXX_REG_SOC, /* State-of-Charge */
BQ27XXX_REG_DCAP, /* Design Capacity */
BQ27XXX_REG_AP, /* Average Power */
+ BQ27XXX_DM_CTRL, /* Block Data Control */
+ BQ27XXX_DM_CLASS, /* Data Class */
+ BQ27XXX_DM_BLOCK, /* Data Block */
+ BQ27XXX_DM_DATA, /* Block Data */
+ BQ27XXX_DM_CKSUM, /* Block Data Checksum */
BQ27XXX_REG_MAX, /* sentinel */
};
+#define BQ27XXX_DM_REG_ROWS \
+ [BQ27XXX_DM_CTRL] = 0x61, \
+ [BQ27XXX_DM_CLASS] = 0x3e, \
+ [BQ27XXX_DM_BLOCK] = 0x3f, \
+ [BQ27XXX_DM_DATA] = 0x40, \
+ [BQ27XXX_DM_CKSUM] = 0x60
+
/* Register mappings */
static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27000] = {
@@ -131,6 +151,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x0b,
[BQ27XXX_REG_DCAP] = 0x76,
[BQ27XXX_REG_AP] = 0x24,
+ [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
},
[BQ27010] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +175,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x0b,
[BQ27XXX_REG_DCAP] = 0x76,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
+ [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
},
[BQ2750X] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +199,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ2751X] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +219,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27500] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27510G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +259,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27510G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +279,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27510G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +299,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27520G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +319,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27520G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +339,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27520G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +359,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27520G4] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +379,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27530] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +399,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27541] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +419,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27545] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +439,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = 0x24,
+ BQ27XXX_DM_REG_ROWS,
},
[BQ27421] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +459,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x1c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x18,
+ BQ27XXX_DM_REG_ROWS,
},
};
@@ -757,6 +801,73 @@ static struct {
static DEFINE_MUTEX(bq27xxx_list_lock);
static LIST_HEAD(bq27xxx_battery_devices);
+#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
+
+#define BQ27XXX_DM_SZ 32
+
+struct bq27xxx_dm_reg {
+ u8 subclass_id;
+ u8 offset;
+ u8 bytes;
+ u16 min, max;
+};
+
+/**
+ * struct bq27xxx_dm_buf - chip data memory buffer
+ * @class: data memory subclass_id
+ * @block: data memory block number
+ * @data: data from/for the block
+ * @has_data: true if data has been filled by read
+ * @dirty: true if data has changed since last read/write
+ *
+ * Encapsulates info required to manage chip data memory blocks.
+ */
+struct bq27xxx_dm_buf {
+ u8 class;
+ u8 block;
+ u8 data[BQ27XXX_DM_SZ];
+ bool has_data, dirty;
+};
+
+#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
+);
+
static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
{
struct bq27xxx_device_info *di;
@@ -794,11 +905,419 @@ MODULE_PARM_DESC(poll_interval,
static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
bool single)
{
- /* Reports EINVAL for invalid/missing registers */
+ int ret;
+
+ if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
+ return -EINVAL;
+
+ ret = di->bus.read(di, di->regs[reg_index], single);
+ if (ret < 0)
+ dev_dbg(di->dev, "failed to read register 0x%02x (index %d)\n",
+ di->regs[reg_index], reg_index);
+
+ return ret;
+}
+
+static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index,
+ u16 value, bool single)
+{
+ int ret;
+
+ if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
+ return -EINVAL;
+
+ if (!di->bus.write)
+ return -EPERM;
+
+ ret = di->bus.write(di, di->regs[reg_index], value, single);
+ if (ret < 0)
+ dev_dbg(di->dev, "failed to write register 0x%02x (index %d)\n",
+ di->regs[reg_index], reg_index);
+
+ return ret;
+}
+
+static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_index,
+ u8 *data, int len)
+{
+ int ret;
+
+ if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
+ return -EINVAL;
+
+ if (!di->bus.read_bulk)
+ return -EPERM;
+
+ ret = di->bus.read_bulk(di, di->regs[reg_index], data, len);
+ if (ret < 0)
+ dev_dbg(di->dev, "failed to read_bulk register 0x%02x (index %d)\n",
+ di->regs[reg_index], reg_index);
+
+ return ret;
+}
+
+static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_index,
+ u8 *data, int len)
+{
+ int ret;
+
if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
return -EINVAL;
- return di->bus.read(di, di->regs[reg_index], single);
+ if (!di->bus.write_bulk)
+ return -EPERM;
+
+ ret = di->bus.write_bulk(di, di->regs[reg_index], data, len);
+ if (ret < 0)
+ dev_dbg(di->dev, "failed to write_bulk register 0x%02x (index %d)\n",
+ di->regs[reg_index], reg_index);
+
+ return ret;
+}
+
+static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
+{
+ int ret;
+
+ ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
+ if (ret < 0) {
+ dev_err(di->dev, "bus error on seal: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
+{
+ int ret;
+
+ if (di->unseal_key == 0) {
+ dev_err(di->dev, "unseal failed due to missing key\n");
+ return -EINVAL;
+ }
+
+ ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
+ if (ret < 0)
+ goto out;
+
+ ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
+ if (ret < 0)
+ goto out;
+
+ return 0;
+
+out:
+ dev_err(di->dev, "bus error on unseal: %d\n", ret);
+ return ret;
+}
+
+static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
+{
+ u16 sum = 0;
+ int i;
+
+ for (i = 0; i < BQ27XXX_DM_SZ; i++)
+ sum += buf->data[i];
+ sum &= 0xff;
+
+ return 0xff - sum;
+}
+
+static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
+ struct bq27xxx_dm_buf *buf)
+{
+ int ret;
+
+ buf->has_data = false;
+
+ ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
+ if (ret < 0)
+ goto out;
+
+ ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
+ if (ret < 0)
+ goto out;
+
+ BQ27XXX_MSLEEP(1);
+
+ ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
+ if (ret < 0)
+ goto out;
+
+ ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
+ if (ret < 0)
+ goto out;
+
+ if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ buf->has_data = true;
+ buf->dirty = false;
+
+ return 0;
+
+out:
+ dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
+ return ret;
+}
+
+static 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;
+ }
+
+ 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;
+ u16 cmd = active ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET;
+ int ret, try = limit;
+
+ ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, cmd, false);
+ if (ret < 0)
+ return ret;
+
+ do {
+ BQ27XXX_MSLEEP(25);
+ ret = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false);
+ if (ret < 0)
+ return ret;
+ } while (!!(ret & BQ27XXX_FLAG_CFGUP) != active && --try);
+
+ if (!try) {
+ dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", active);
+ return -EINVAL;
+ }
+
+ if (limit - try > 3)
+ dev_warn(di->dev, "cfgupdate %d, retries %d\n", active, limit - try);
+
+ return 0;
+}
+
+static inline int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di)
+{
+ int ret = bq27xxx_battery_cfgupdate_priv(di, true);
+ if (ret < 0 && ret != -EINVAL)
+ dev_err(di->dev, "bus error on set_cfgupdate: %d\n", ret);
+
+ return ret;
+}
+
+static inline int bq27xxx_battery_soft_reset(struct bq27xxx_device_info *di)
+{
+ int ret = bq27xxx_battery_cfgupdate_priv(di, false);
+ if (ret < 0 && ret != -EINVAL)
+ dev_err(di->dev, "bus error on soft_reset: %d\n", ret);
+
+ return ret;
+}
+
+static int bq27xxx_battery_write_dm_block