diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-04 14:25:14 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-04 14:25:14 -0700 |
commit | a897a10141ae4deb43346fbf2ae2a5149bd7fd5a (patch) | |
tree | 14dce2d8e155ca50e19c3ac5e3563fe55ca0cc67 /drivers | |
parent | eceeae414edccb07fad683011c0c65abe8a3a554 (diff) | |
parent | bfa953d336cdd713f6968c85ca820ef22333dc35 (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/Kconfig | 4 | ||||
-rw-r--r-- | drivers/power/reset/at91-poweroff.c | 2 | ||||
-rw-r--r-- | drivers/power/reset/at91-sama5d2_shdwc.c | 2 | ||||
-rw-r--r-- | drivers/power/reset/reboot-mode.c | 2 | ||||
-rw-r--r-- | drivers/power/reset/reboot-mode.h | 18 | ||||
-rw-r--r-- | drivers/power/reset/syscon-reboot-mode.c | 2 | ||||
-rw-r--r-- | drivers/power/supply/Kconfig | 26 | ||||
-rw-r--r-- | drivers/power/supply/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/supply/axp20x_battery.c | 88 | ||||
-rw-r--r-- | drivers/power/supply/axp20x_usb_power.c | 2 | ||||
-rw-r--r-- | drivers/power/supply/bq24735-charger.c | 6 | ||||
-rw-r--r-- | drivers/power/supply/bq27xxx_battery.c | 536 | ||||
-rw-r--r-- | drivers/power/supply/bq27xxx_battery_i2c.c | 82 | ||||
-rw-r--r-- | drivers/power/supply/cpcap-battery.c | 808 | ||||
-rw-r--r-- | drivers/power/supply/cpcap-charger.c | 89 | ||||
-rw-r--r-- | drivers/power/supply/ltc3651-charger.c | 210 | ||||
-rw-r--r-- | drivers/power/supply/power_supply_core.c | 83 | ||||
-rw-r--r-- | drivers/power/supply/power_supply_sysfs.c | 125 | ||||
-rw-r--r-- | drivers/power/supply/sbs-battery.c | 29 | ||||
-rw-r--r-- | drivers/power/supply/twl4030_charger.c | 97 |
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, ¤t_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 |