diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-04 23:25:14 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-04 23:25:14 +0200 |
commit | a897a10141ae4deb43346fbf2ae2a5149bd7fd5a (patch) | |
tree | 14dce2d8e155ca50e19c3ac5e3563fe55ca0cc67 /drivers/power | |
parent | Merge tag 'acpi-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ra... (diff) | |
parent | power: supply: sbs-battery: Don't needlessly set CAPACITY_MODE (diff) | |
download | linux-a897a10141ae4deb43346fbf2ae2a5149bd7fd5a.tar.xz linux-a897a10141ae4deb43346fbf2ae2a5149bd7fd5a.zip |
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/power')
-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(struct bq27xxx_device_info *di, + struct bq27xxx_dm_buf *buf) +{ + bool cfgup = di->chip == BQ27421; /* assume related chips need cfgupdate */ + int ret; + + if (!buf->dirty) + return 0; + + if (cfgup) { + ret = bq27xxx_battery_set_cfgupdate(di); + if (ret < 0) + return ret; + } + + ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true); + if (ret < 0) + goto out; + + 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_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM, + bq27xxx_battery_checksum_dm_block(buf), true); + if (ret < 0) + goto out; + + /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM + * corruption on the '425 chip (and perhaps others), which can damage + * the chip. + */ + + if (cfgup) { + BQ27XXX_MSLEEP(1); + ret = bq27xxx_battery_soft_reset(di); + if (ret < 0) + return ret; + } else { + BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */ + } + + buf->dirty = false; + + return 0; + +out: + if (cfgup) + bq27xxx_battery_soft_reset(di); + + dev_err(di->dev, "bus error writing chip memory: %d\n", ret); + 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); } /* @@ -1318,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; @@ -1351,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); @@ -1376,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/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index c68fbc3fe50a..a5972214f074 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -38,7 +38,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, { struct i2c_client *client = to_i2c_client(di->dev); struct i2c_msg msg[2]; - unsigned char data[2]; + u8 data[2]; int ret; if (!client->adapter) @@ -68,6 +68,82 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, return ret; } +static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg, + int value, bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg; + u8 data[4]; + int ret; + + if (!client->adapter) + return -ENODEV; + + data[0] = reg; + if (single) { + data[1] = (u8) value; + msg.len = 2; + } else { + put_unaligned_le16(value, &data[1]); + msg.len = 3; + } + + msg.buf = data; + msg.addr = client->addr; + msg.flags = 0; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EINVAL; + return 0; +} + +static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg, + u8 *data, int len) +{ + struct i2c_client *client = to_i2c_client(di->dev); + int ret; + + if (!client->adapter) + return -ENODEV; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, data); + if (ret < 0) + return ret; + if (ret != len) + return -EINVAL; + return 0; +} + +static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di, + u8 reg, u8 *data, int len) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg; + u8 buf[33]; + int ret; + + if (!client->adapter) + return -ENODEV; + + buf[0] = reg; + memcpy(&buf[1], data, len); + + msg.buf = buf; + msg.addr = client->addr; + msg.flags = 0; + msg.len = len + 1; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EINVAL; + return 0; +} + static int bq27xxx_battery_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -95,7 +171,11 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client, di->dev = &client->dev; di->chip = id->driver_data; di->name = name; + di->bus.read = bq27xxx_battery_i2c_read; + di->bus.write = bq27xxx_battery_i2c_write; + di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read; + di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write; ret = bq27xxx_battery_setup(di); if (ret) diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c new file mode 100644 index 000000000000..ee71a2b37b12 --- /dev/null +++ b/drivers/power/supply/cpcap-battery.c @@ -0,0 +1,808 @@ +/* + * Battery driver for CPCAP PMIC + * + * Copyright (C) 2017 Tony Lindgren <tony@atomide.com> + * + * Some parts of the code based on earlie Motorola mapphone Linux kernel + * drivers: + * + * Copyright (C) 2009-2010 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +#include <linux/iio/consumer.h> +#include <linux/iio/types.h> +#include <linux/mfd/motorola-cpcap.h> + +#include <asm/div64.h> + +/* + * Register bit defines for CPCAP_REG_BPEOL. Some of these seem to + * map to MC13783UG.pdf "Table 5-19. Register 13, Power Control 0" + * to enable BATTDETEN, LOBAT and EOL features. We currently use + * LOBAT interrupts instead of EOL. + */ +#define CPCAP_REG_BPEOL_BIT_EOL9 BIT(9) /* Set for EOL irq */ +#define CPCAP_REG_BPEOL_BIT_EOL8 BIT(8) /* Set for EOL irq */ +#define CPCAP_REG_BPEOL_BIT_UNKNOWN7 BIT(7) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN6 BIT(6) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN5 BIT(5) +#define CPCAP_REG_BPEOL_BIT_EOL_MULTI BIT(4) /* Set for multiple EOL irqs */ +#define CPCAP_REG_BPEOL_BIT_UNKNOWN3 BIT(3) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN2 BIT(2) +#define CPCAP_REG_BPEOL_BIT_BATTDETEN BIT(1) /* Enable battery detect */ +#define CPCAP_REG_BPEOL_BIT_EOLSEL BIT(0) /* BPDET = 0, EOL = 1 */ + +#define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250 + +enum { + CPCAP_BATTERY_IIO_BATTDET, + CPCAP_BATTERY_IIO_VOLTAGE, + CPCAP_BATTERY_IIO_CHRG_CURRENT, + CPCAP_BATTERY_IIO_BATT_CURRENT, + CPCAP_BATTERY_IIO_NR, +}; + +enum cpcap_battery_irq_action { + CPCAP_BATTERY_IRQ_ACTION_NONE, + CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW, + CPCAP_BATTERY_IRQ_ACTION_POWEROFF, +}; + +struct cpcap_interrupt_desc { + const char *name; + struct list_head node; + int irq; + enum cpcap_battery_irq_action action; +}; + +struct cpcap_battery_config { + int ccm; + int cd_factor; + struct power_supply_info info; +}; + +struct cpcap_coulomb_counter_data { + s32 sample; /* 24-bits */ + s32 accumulator; + s16 offset; /* 10-bits */ +}; + +enum cpcap_battery_state { + CPCAP_BATTERY_STATE_PREVIOUS, + CPCAP_BATTERY_STATE_LATEST, + CPCAP_BATTERY_STATE_NR, +}; + +struct cpcap_battery_state_data { + int voltage; + int current_ua; + int counter_uah; + int temperature; + ktime_t time; + struct cpcap_coulomb_counter_data cc; +}; + +struct cpcap_battery_ddata { + struct device *dev; + struct regmap *reg; + struct list_head irq_list; + struct iio_channel *channels[CPCAP_BATTERY_IIO_NR]; + struct power_supply *psy; + struct cpcap_battery_config config; + struct cpcap_battery_state_data state[CPCAP_BATTERY_STATE_NR]; + atomic_t active; + int status; + u16 vendor; +}; + +#define CPCAP_NO_BATTERY -400 + +static struct cpcap_battery_state_data * +cpcap_battery_get_state(struct cpcap_battery_ddata *ddata, + enum cpcap_battery_state state) +{ + if (state >= CPCAP_BATTERY_STATE_NR) + return NULL; + + return &ddata->state[state]; +} + +static struct cpcap_battery_state_data * +cpcap_battery_latest(struct cpcap_battery_ddata *ddata) +{ + return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_LATEST); +} + +static struct cpcap_battery_state_data * +cpcap_battery_previous(struct cpcap_battery_ddata *ddata) +{ + return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_PREVIOUS); +} + +static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata, + int *value) +{ + struct iio_channel *channel; + int error; + + channel = ddata->channels[CPCAP_BATTERY_IIO_BATTDET]; + error = iio_read_channel_processed(channel, value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + *value = CPCAP_NO_BATTERY; + + return error; + } + + *value /= 100; + + return 0; +} + +static int cpcap_battery_get_voltage(struct cpcap_battery_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_BATTERY_IIO_VOLTAGE]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value * 1000; +} + +static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_BATTERY_IIO_BATT_CURRENT]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value * 1000; +} + +/** + * cpcap_battery_cc_raw_div - calculate and divide coulomb counter μAms values + * @ddata: device driver data + * @sample: coulomb counter sample value + * @accumulator: coulomb counter integrator value + * @offset: coulomb counter offset value + * @divider: conversion divider + * + * Note that cc_lsb and cc_dur values are from Motorola Linux kernel + * function data_get_avg_curr_ua() and seem to be based on measured test + * results. It also has the following comment: + * + * Adjustment factors are applied here as a temp solution per the test + * results. Need to work out a formal solution for this adjustment. + * + * A coulomb counter for similar hardware seems to be documented in + * "TWL6030 Gas Gauging Basics (Rev. A)" swca095a.pdf in chapter + * "10 Calculating Accumulated Current". We however follow what the + * Motorola mapphone Linux kernel is doing as there may be either a + * TI or ST coulomb counter in the PMIC. + */ +static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset, u32 divider) +{ + s64 acc; + u64 tmp; + int avg_current; + u32 cc_lsb; + + sample &= 0xffffff; /* 24-bits, unsigned */ + offset &= 0x7ff; /* 10-bits, signed */ + + switch (ddata->vendor) { + case CPCAP_VENDOR_ST: + cc_lsb = 95374; /* μAms per LSB */ + break; + case CPCAP_VENDOR_TI: + cc_lsb = 91501; /* μAms per LSB */ + break; + default: + return -EINVAL; + } + + acc = accumulator; + acc = acc - ((s64)sample * offset); + cc_lsb = (cc_lsb * ddata->config.cd_factor) / 1000; + + if (acc >= 0) + tmp = acc; + else + tmp = acc * -1; + + tmp = tmp * cc_lsb; + do_div(tmp, divider); + avg_current = tmp; + + if (acc >= 0) + return -avg_current; + else + return avg_current; +} + +/* 3600000μAms = 1μAh */ +static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset) +{ + return cpcap_battery_cc_raw_div(ddata, sample, + accumulator, offset, + 3600000); +} + +static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset) +{ + return cpcap_battery_cc_raw_div(ddata, sample, + accumulator, offset, + sample * + CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS); +} + +/** + * cpcap_battery_read_accumulated - reads cpcap coulomb counter + * @ddata: device driver data + * @regs: coulomb counter values + * + * Based on Motorola mapphone kernel function data_read_regs(). + * Looking at the registers, the coulomb counter seems similar to + * the coulomb counter in TWL6030. See "TWL6030 Gas Gauging Basics + * (Rev. A) swca095a.pdf for "10 Calculating Accumulated Current". + * + * Note that swca095a.pdf instructs to stop the coulomb counter + * before reading to avoid values changing. Motorola mapphone + * Linux kernel does not do it, so let's assume they've verified + * the data produced is correct. + */ +static int +cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, + struct cpcap_coulomb_counter_data *ccd) +{ + u16 buf[7]; /* CPCAP_REG_CC1 to CCI */ + int error; + + ccd->sample = 0; + ccd->accumulator = 0; + ccd->offset = 0; + + /* Read coulomb counter register range */ + error = regmap_bulk_read(ddata->reg, CPCAP_REG_CCS1, + buf, ARRAY_SIZE(buf)); + if (error) + return 0; + + /* Sample value CPCAP_REG_CCS1 & 2 */ + ccd->sample = (buf[1] & 0x0fff) << 16; + ccd->sample |= buf[0]; + + /* Accumulator value CPCAP_REG_CCA1 & 2 */ + ccd->accumulator = ((s16)buf[3]) << 16; + ccd->accumulator |= buf[2]; + + /* Offset value CPCAP_REG_CCO */ + ccd->offset = buf[5]; + + /* Adjust offset based on mode value CPCAP_REG_CCM? */ + if (buf[4] >= 0x200) + ccd->offset |= 0xfc00; + + return cpcap_battery_cc_to_uah(ddata, + ccd->sample, + ccd->accumulator, + ccd->offset); +} + +/** + * cpcap_battery_cc_get_avg_current - read cpcap coulumb counter + * @ddata: cpcap battery driver device data + */ +static int cpcap_battery_cc_get_avg_current(struct cpcap_battery_ddata *ddata) +{ + int value, acc, error; + s32 sample = 1; + s16 offset; + + if (ddata->vendor == CPCAP_VENDOR_ST) + sample = 4; + + /* Coulomb counter integrator */ + error = regmap_read(ddata->reg, CPCAP_REG_CCI, &value); + if (error) + return error; + + if ((ddata->vendor == CPCAP_VENDOR_TI) && (value > 0x2000)) + value = value | 0xc000; + + acc = (s16)value; + + /* Coulomb counter sample time */ + error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value); + if (error) + return error; + + if (value < 0x200) + offset = value; + else + offset = value | 0xfc00; + + return cpcap_battery_cc_to_ua(ddata, sample, acc, offset); +} + +static bool cpcap_battery_full(struct cpcap_battery_ddata *ddata) +{ + struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata); + + /* Basically anything that measures above 4347000 is full */ + if (state->voltage >= (ddata->config.info.voltage_max_design - 4000)) + return true; + + return false; +} + +static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata) +{ + struct cpcap_battery_state_data state, *latest, *previous; + ktime_t now; + int error; + + memset(&state, 0, sizeof(state)); + now = ktime_get(); + + latest = cpcap_battery_latest(ddata); + if (latest) { + s64 delta_ms = ktime_to_ms(ktime_sub(now, latest->time)); + + if (delta_ms < CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS) + return delta_ms; + } + + state.time = now; + state.voltage = cpcap_battery_get_voltage(ddata); + state.current_ua = cpcap_battery_get_current(ddata); + state.counter_uah = cpcap_battery_read_accumulated(ddata, &state.cc); + + error = cpcap_charger_battery_temperature(ddata, + &state.temperature); + if (error) + return error; + + previous = cpcap_battery_previous(ddata); + memcpy(previous, latest, sizeof(*previous)); + memcpy(latest, &state, sizeof(*latest)); + + return 0; +} + +static enum power_supply_property cpcap_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_TEMP, +}; + +static int cpcap_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy); + struct cpcap_battery_state_data *latest, *previous; + u32 sample; + s32 accumulator; + int cached; + s64 tmp; + + cached = cpcap_battery_update_status(ddata); + if (cached < 0) + return cached; + + latest = cpcap_battery_latest(ddata); + previous = cpcap_battery_previous(ddata); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + if (latest->temperature > CPCAP_NO_BATTERY) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (cpcap_battery_full(ddata)) { + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + } + if (cpcap_battery_cc_get_avg_current(ddata) < 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = ddata->config.info.technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = cpcap_battery_get_voltage(ddata); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = ddata->config.info.voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = ddata->config.info.voltage_min_design; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + if (cached) { + val->intval = cpcap_battery_cc_get_avg_current(ddata); + break; + } + sample = latest->cc.sample - previous->cc.sample; + accumulator = latest->cc.accumulator - previous->cc.accumulator; + val->intval = cpcap_battery_cc_to_ua(ddata, sample, + accumulator, + latest->cc.offset); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = latest->current_ua; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = latest->counter_uah; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + tmp = (latest->voltage / 10000) * latest->current_ua; + val->intval = div64_s64(tmp, 100); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + if (cached) { + tmp = cpcap_battery_cc_get_avg_current(ddata); + tmp *= (latest->voltage / 10000); + val->intval = div64_s64(tmp, 100); + break; + } + sample = latest->cc.sample - previous->cc.sample; + accumulator = latest->cc.accumulator - previous->cc.accumulator; + tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator, + latest->cc.offset); + tmp *= ((latest->voltage + previous->voltage) / 20000); + val->intval = div64_s64(tmp, 100); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (cpcap_battery_full(ddata)) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (latest->voltage >= 3750000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else if (latest->voltage >= 3300000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (latest->voltage > 3100000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (latest->voltage <= 3100000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = ddata->config.info.charge_full_design; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = latest->temperature; + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) +{ + struct cpcap_battery_ddata *ddata = data; + struct cpcap_battery_state_data *latest; + struct cpcap_interrupt_desc *d; + + if (!atomic_read(&ddata->active)) + return IRQ_NONE; + + list_for_each_entry(d, &ddata->irq_list, node) { + if (irq == d->irq) + break; + } + + if (!d) + return IRQ_NONE; + + latest = cpcap_battery_latest(ddata); + + switch (d->action) { + case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW: + if (latest->counter_uah >= 0) + dev_warn(ddata->dev, "Battery low at 3.3V!\n"); + break; + case CPCAP_BATTERY_IRQ_ACTION_POWEROFF: + if (latest->counter_uah >= 0) { + dev_emerg(ddata->dev, + "Battery empty at 3.1V, powering off\n"); + orderly_poweroff(true); + } + break; + default: + break; + } + + power_supply_changed(ddata->psy); + + return IRQ_HANDLED; +} + +static int cpcap_battery_init_irq(struct platform_device *pdev, + struct cpcap_battery_ddata *ddata, + const char *name) +{ + struct cpcap_interrupt_desc *d; + int irq, error; + + irq = platform_get_irq_byname(pdev, name); + if (!irq) + return -ENODEV; + + error = devm_request_threaded_irq(ddata->dev, irq, NULL, + cpcap_battery_irq_thread, + IRQF_SHARED, + name, ddata); + if (error) { + dev_err(ddata->dev, "could not get irq %s: %i\n", + name, error); + + return error; + } + + d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->name = name; + d->irq = irq; + + if (!strncmp(name, "lowbph", 6)) + d->action = CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW; + else if (!strncmp(name, "lowbpl", 6)) + d->action = CPCAP_BATTERY_IRQ_ACTION_POWEROFF; + + list_add(&d->node, &ddata->irq_list); + + return 0; +} + +static int cpcap_battery_init_interrupts(struct platform_device *pdev, + struct cpcap_battery_ddata *ddata) +{ + const char * const cpcap_battery_irqs[] = { + "eol", "lowbph", "lowbpl", + "chrgcurr1", "battdetb" + }; + int i, error; + + for (i = 0; i < ARRAY_SIZE(cpcap_battery_irqs); i++) { + error = cpcap_battery_init_irq(pdev, ddata, + cpcap_battery_irqs[i]); + if (error) + return error; + } + + /* Enable low battery interrupts for 3.3V high and 3.1V low */ + error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, + 0xffff, + CPCAP_REG_BPEOL_BIT_BATTDETEN); + if (error) + return error; + + return 0; +} + +static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) +{ + const char * const names[CPCAP_BATTERY_IIO_NR] = { + "battdetb", "battp", "chg_isense", "batti", + }; + int error, i; + + for (i = 0; i < CPCAP_BATTERY_IIO_NR; i++) { + ddata->channels[i] = devm_iio_channel_get(ddata->dev, + names[i]); + if (IS_ERR(ddata->channels[i])) { + error = PTR_ERR(ddata->channels[i]); + goto out_err; + } + + if (!ddata->channels[i]->indio_dev) { + error = -ENXIO; + goto out_err; + } + } + + return 0; + +out_err: + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); + + return error; +} + +/* + * Based on the values from Motorola mapphone Linux kernel. In the + * the Motorola mapphone Linux kernel tree the value for pm_cd_factor + * is passed to the kernel via device tree. If it turns out to be + * something device specific we can consider that too later. + * + * And looking at the battery full and shutdown values for the stock + * kernel on droid 4, full is 4351000 and software initiates shutdown + * at 3078000. The device will die around 2743000. + */ +static const struct cpcap_battery_config cpcap_battery_default_data = { + .ccm = 0x3ff, + .cd_factor = 0x3cc, + .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, + .info.voltage_max_design = 4351000, + .info.voltage_min_design = 3100000, + .info.charge_full_design = 1740000, +}; + +#ifdef CONFIG_OF +static const struct of_device_id cpcap_battery_id_table[] = { + { + .compatible = "motorola,cpcap-battery", + .data = &cpcap_battery_default_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_battery_id_table); +#endif + +static int cpcap_battery_probe(struct platform_device *pdev) +{ + struct power_supply_desc *psy_desc; + struct cpcap_battery_ddata *ddata; + const struct of_device_id *match; + struct power_supply_config psy_cfg = {}; + int error; + + match = of_match_device(of_match_ptr(cpcap_battery_id_table), + &pdev->dev); + if (!match) + return -EINVAL; + + if (!match->data) { + dev_err(&pdev->dev, "no configuration data found\n"); + + return -ENODEV; + } + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + INIT_LIST_HEAD(&ddata->irq_list); + ddata->dev = &pdev->dev; + memcpy(&ddata->config, match->data, sizeof(ddata->config)); + + ddata->reg = dev_get_regmap(ddata->dev->parent, NULL); + if (!ddata->reg) + return -ENODEV; + + error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor); + if (error) + return error; + + platform_set_drvdata(pdev, ddata); + + error = regmap_update_bits(ddata->reg, CPCAP_REG_CCM, + 0xffff, ddata->config.ccm); + if (error) + return error; + + error = cpcap_battery_init_interrupts(pdev, ddata); + if (error) + return error; + + error = cpcap_battery_init_iio(ddata); + if (error) + return error; + + psy_desc = devm_kzalloc(ddata->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = "battery", + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY, + psy_desc->properties = cpcap_battery_props, + psy_desc->num_properties = ARRAY_SIZE(cpcap_battery_props), + psy_desc->get_property = cpcap_battery_get_property, + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ddata; + + ddata->psy = devm_power_supply_register(ddata->dev, psy_desc, + &psy_cfg); + error = PTR_ERR_OR_ZERO(ddata->psy); + if (error) { + dev_err(ddata->dev, "failed to register power supply\n"); + return error; + } + + atomic_set(&ddata->active, 1); + + return 0; +} + +static int cpcap_battery_remove(struct platform_device *pdev) +{ + struct cpcap_battery_ddata *ddata = platform_get_drvdata(pdev); + int error; + + atomic_set(&ddata->active, 0); + error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, + 0xffff, 0); + if (error) + dev_err(&pdev->dev, "could not disable: %i\n", error); + + return 0; +} + +static struct platform_driver cpcap_battery_driver = { + .driver = { + .name = "cpcap_battery", + .of_match_table = of_match_ptr(cpcap_battery_id_table), + }, + .probe = cpcap_battery_probe, + .remove = cpcap_battery_remove, +}; +module_platform_driver(cpcap_battery_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("CPCAP PMIC Battery Driver"); diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 26a2dc7ac9a2..11a07633de6c 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -38,20 +38,27 @@ #include <linux/iio/consumer.h> #include <linux/mfd/motorola-cpcap.h> -/* CPCAP_REG_CRM register bits */ +/* + * CPCAP_REG_CRM register bits. For documentation of somewhat similar hardware, + * see NXP "MC13783 Power Management and Audio Circuit Users's Guide" + * MC13783UG.pdf chapter "8.5 Battery Interface Register Summary". The registers + * and values for CPCAP are different, but some of the internal components seem + * similar. Also see the Motorola Linux kernel cpcap-regbits.h. CPCAP_REG_CHRGR_1 + * bits that seem to describe the CRM register. + */ #define CPCAP_REG_CRM_UNUSED_641_15 BIT(15) /* 641 = register number */ #define CPCAP_REG_CRM_UNUSED_641_14 BIT(14) /* 641 = register number */ -#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) -#define CPCAP_REG_CRM_RVRSMODE BIT(12) -#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) +#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) /* Charger LED */ +#define CPCAP_REG_CRM_RVRSMODE BIT(12) /* USB VBUS output enable */ +#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) /* Trickle charge current */ #define CPCAP_REG_CRM_ICHRG_TR0 BIT(10) -#define CPCAP_REG_CRM_FET_OVRD BIT(9) -#define CPCAP_REG_CRM_FET_CTRL BIT(8) -#define CPCAP_REG_CRM_VCHRG3 BIT(7) +#define CPCAP_REG_CRM_FET_OVRD BIT(9) /* 0 = hardware, 1 = FET_CTRL */ +#define CPCAP_REG_CRM_FET_CTRL BIT(8) /* BPFET 1 if FET_OVRD set */ +#define CPCAP_REG_CRM_VCHRG3 BIT(7) /* Charge voltage bits */ #define CPCAP_REG_CRM_VCHRG2 BIT(6) #define CPCAP_REG_CRM_VCHRG1 BIT(5) #define CPCAP_REG_CRM_VCHRG0 BIT(4) -#define CPCAP_REG_CRM_ICHRG3 BIT(3) +#define CPCAP_REG_CRM_ICHRG3 BIT(3) /* Charge current bits */ #define CPCAP_REG_CRM_ICHRG2 BIT(2) #define CPCAP_REG_CRM_ICHRG1 BIT(1) #define CPCAP_REG_CRM_ICHRG0 BIT(0) @@ -63,42 +70,50 @@ #define CPCAP_REG_CRM_TR_0A48 CPCAP_REG_CRM_TR(0x2) #define CPCAP_REG_CRM_TR_0A72 CPCAP_REG_CRM_TR(0x4) -/* CPCAP_REG_CRM charge voltages */ +/* + * CPCAP_REG_CRM charge voltages based on the ADC channel 1 values. + * Note that these register bits don't match MC13783UG.pdf VCHRG + * register bits. + */ #define CPCAP_REG_CRM_VCHRG(val) (((val) & 0xf) << 4) #define CPCAP_REG_CRM_VCHRG_3V80 CPCAP_REG_CRM_VCHRG(0x0) #define CPCAP_REG_CRM_VCHRG_4V10 CPCAP_REG_CRM_VCHRG(0x1) -#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x2) -#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x3) -#define CPCAP_REG_CRM_VCHRG_4V22 CPCAP_REG_CRM_VCHRG(0x4) -#define CPCAP_REG_CRM_VCHRG_4V24 CPCAP_REG_CRM_VCHRG(0x5) -#define CPCAP_REG_CRM_VCHRG_4V26 CPCAP_REG_CRM_VCHRG(0x6) -#define CPCAP_REG_CRM_VCHRG_4V28 CPCAP_REG_CRM_VCHRG(0x7) -#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x8) -#define CPCAP_REG_CRM_VCHRG_4V32 CPCAP_REG_CRM_VCHRG(0x9) -#define CPCAP_REG_CRM_VCHRG_4V34 CPCAP_REG_CRM_VCHRG(0xa) +#define CPCAP_REG_CRM_VCHRG_4V12 CPCAP_REG_CRM_VCHRG(0x2) +#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x3) +#define CPCAP_REG_CRM_VCHRG_4V17 CPCAP_REG_CRM_VCHRG(0x4) +#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x5) +#define CPCAP_REG_CRM_VCHRG_4V23 CPCAP_REG_CRM_VCHRG(0x6) +#define CPCAP_REG_CRM_VCHRG_4V25 CPCAP_REG_CRM_VCHRG(0x7) +#define CPCAP_REG_CRM_VCHRG_4V27 CPCAP_REG_CRM_VCHRG(0x8) +#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x9) +#define CPCAP_REG_CRM_VCHRG_4V33 CPCAP_REG_CRM_VCHRG(0xa) #define CPCAP_REG_CRM_VCHRG_4V35 CPCAP_REG_CRM_VCHRG(0xb) #define CPCAP_REG_CRM_VCHRG_4V38 CPCAP_REG_CRM_VCHRG(0xc) #define CPCAP_REG_CRM_VCHRG_4V40 CPCAP_REG_CRM_VCHRG(0xd) #define CPCAP_REG_CRM_VCHRG_4V42 CPCAP_REG_CRM_VCHRG(0xe) #define CPCAP_REG_CRM_VCHRG_4V44 CPCAP_REG_CRM_VCHRG(0xf) -/* CPCAP_REG_CRM charge currents */ +/* + * CPCAP_REG_CRM charge currents. These seem to match MC13783UG.pdf + * values in "Table 8-3. Charge Path Regulator Current Limit + * Characteristics" for the nominal values. + */ #define CPCAP_REG_CRM_ICHRG(val) (((val) & 0xf) << 0) #define CPCAP_REG_CRM_ICHRG_0A000 CPCAP_REG_CRM_ICHRG(0x0) #define CPCAP_REG_CRM_ICHRG_0A070 CPCAP_REG_CRM_ICHRG(0x1) -#define CPCAP_REG_CRM_ICHRG_0A176 CPCAP_REG_CRM_ICHRG(0x2) -#define CPCAP_REG_CRM_ICHRG_0A264 CPCAP_REG_CRM_ICHRG(0x3) -#define CPCAP_REG_CRM_ICHRG_0A352 CPCAP_REG_CRM_ICHRG(0x4) -#define CPCAP_REG_CRM_ICHRG_0A440 CPCAP_REG_CRM_ICHRG(0x5) -#define CPCAP_REG_CRM_ICHRG_0A528 CPCAP_REG_CRM_ICHRG(0x6) -#define CPCAP_REG_CRM_ICHRG_0A616 CPCAP_REG_CRM_ICHRG(0x7) -#define CPCAP_REG_CRM_ICHRG_0A704 CPCAP_REG_CRM_ICHRG(0x8) -#define CPCAP_REG_CRM_ICHRG_0A792 CPCAP_REG_CRM_ICHRG(0x9) -#define CPCAP_REG_CRM_ICHRG_0A880 CPCAP_REG_CRM_ICHRG(0xa) -#define CPCAP_REG_CRM_ICHRG_0A968 CPCAP_REG_CRM_ICHRG(0xb) -#define CPCAP_REG_CRM_ICHRG_1A056 CPCAP_REG_CRM_ICHRG(0xc) -#define CPCAP_REG_CRM_ICHRG_1A144 CPCAP_REG_CRM_ICHRG(0xd) -#define CPCAP_REG_CRM_ICHRG_1A584 CPCAP_REG_CRM_ICHRG(0xe) +#define CPCAP_REG_CRM_ICHRG_0A177 CPCAP_REG_CRM_ICHRG(0x2) +#define CPCAP_REG_CRM_ICHRG_0A266 CPCAP_REG_CRM_ICHRG(0x3) +#define CPCAP_REG_CRM_ICHRG_0A355 CPCAP_REG_CRM_ICHRG(0x4) +#define CPCAP_REG_CRM_ICHRG_0A443 CPCAP_REG_CRM_ICHRG(0x5) +#define CPCAP_REG_CRM_ICHRG_0A532 CPCAP_REG_CRM_ICHRG(0x6) +#define CPCAP_REG_CRM_ICHRG_0A621 CPCAP_REG_CRM_ICHRG(0x7) +#define CPCAP_REG_CRM_ICHRG_0A709 CPCAP_REG_CRM_ICHRG(0x8) +#define CPCAP_REG_CRM_ICHRG_0A798 CPCAP_REG_CRM_ICHRG(0x9) +#define CPCAP_REG_CRM_ICHRG_0A886 CPCAP_REG_CRM_ICHRG(0xa) +#define CPCAP_REG_CRM_ICHRG_0A975 CPCAP_REG_CRM_ICHRG(0xb) +#define CPCAP_REG_CRM_ICHRG_1A064 CPCAP_REG_CRM_ICHRG(0xc) +#define CPCAP_REG_CRM_ICHRG_1A152 CPCAP_REG_CRM_ICHRG(0xd) +#define CPCAP_REG_CRM_ICHRG_1A596 CPCAP_REG_CRM_ICHRG(0xe) #define CPCAP_REG_CRM_ICHRG_NO_LIMIT CPCAP_REG_CRM_ICHRG(0xf) enum { @@ -428,9 +443,9 @@ static void cpcap_usb_detect(struct work_struct *work) int max_current; if (cpcap_charger_battery_found(ddata)) - max_current = CPCAP_REG_CRM_ICHRG_1A584; + max_current = CPCAP_REG_CRM_ICHRG_1A596; else - max_current = CPCAP_REG_CRM_ICHRG_0A528; + max_current = CPCAP_REG_CRM_ICHRG_0A532; error = cpcap_charger_set_state(ddata, CPCAP_REG_CRM_VCHRG_4V35, @@ -586,6 +601,7 @@ static int cpcap_charger_probe(struct platform_device *pdev) { struct cpcap_charger_ddata *ddata; const struct of_device_id *of_id; + struct power_supply_config psy_cfg = {}; int error; of_id = of_match_device(of_match_ptr(cpcap_charger_id_table), @@ -614,9 +630,12 @@ static int cpcap_charger_probe(struct platform_device *pdev) atomic_set(&ddata->active, 1); + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ddata; + ddata->usb = devm_power_supply_register(ddata->dev, &cpcap_charger_usb_desc, - NULL); + &psy_cfg); if (IS_ERR(ddata->usb)) { error = PTR_ERR(ddata->usb); dev_err(ddata->dev, "failed to register USB charger: %i\n", diff --git a/drivers/power/supply/ltc3651-charger.c b/drivers/power/supply/ltc3651-charger.c new file mode 100644 index 000000000000..eea63ff211c4 --- /dev/null +++ b/drivers/power/supply/ltc3651-charger.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017, Topic Embedded Products + * Driver for LTC3651 charger IC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/of.h> + +struct ltc3651_charger { + struct power_supply *charger; + struct power_supply_desc charger_desc; + struct gpio_desc *acpr_gpio; + struct gpio_desc *fault_gpio; + struct gpio_desc *chrg_gpio; +}; + +static irqreturn_t ltc3651_charger_irq(int irq, void *devid) +{ + struct power_supply *charger = devid; + + power_supply_changed(charger); + + return IRQ_HANDLED; +} + +static inline struct ltc3651_charger *psy_to_ltc3651_charger( + struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static int ltc3651_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!ltc3651_charger->chrg_gpio) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + if (gpiod_get_value(ltc3651_charger->chrg_gpio)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio); + break; + case POWER_SUPPLY_PROP_HEALTH: + if (!ltc3651_charger->fault_gpio) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + if (!gpiod_get_value(ltc3651_charger->fault_gpio)) { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + } + /* + * If the fault pin is active, the chrg pin explains the type + * of failure. + */ + if (!ltc3651_charger->chrg_gpio) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ? + POWER_SUPPLY_HEALTH_OVERHEAT : + POWER_SUPPLY_HEALTH_DEAD; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property ltc3651_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, +}; + +static int ltc3651_charger_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + struct ltc3651_charger *ltc3651_charger; + struct power_supply_desc *charger_desc; + int ret; + + ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger), + GFP_KERNEL); + if (!ltc3651_charger) + return -ENOMEM; + + ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, + "lltc,acpr", GPIOD_IN); + if (IS_ERR(ltc3651_charger->acpr_gpio)) { + ret = PTR_ERR(ltc3651_charger->acpr_gpio); + dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); + return ret; + } + ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, + "lltc,fault", GPIOD_IN); + if (IS_ERR(ltc3651_charger->fault_gpio)) { + ret = PTR_ERR(ltc3651_charger->fault_gpio); + dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); + return ret; + } + ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, + "lltc,chrg", GPIOD_IN); + if (IS_ERR(ltc3651_charger->chrg_gpio)) { + ret = PTR_ERR(ltc3651_charger->chrg_gpio); + dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); + return ret; + } + + charger_desc = <c3651_charger->charger_desc; + charger_desc->name = pdev->dev.of_node->name; + charger_desc->type = POWER_SUPPLY_TYPE_MAINS; + charger_desc->properties = ltc3651_charger_properties; + charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties); + charger_desc->get_property = ltc3651_charger_get_property; + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ltc3651_charger; + + ltc3651_charger->charger = devm_power_supply_register(&pdev->dev, + charger_desc, &psy_cfg); + if (IS_ERR(ltc3651_charger->charger)) { + ret = PTR_ERR(ltc3651_charger->charger); + dev_err(&pdev->dev, "Failed to register power supply: %d\n", + ret); + return ret; + } + + /* + * Acquire IRQs for the GPIO pins if possible. If the system does not + * support IRQs on these pins, userspace will have to poll the sysfs + * files manually. + */ + if (ltc3651_charger->acpr_gpio) { + ret = gpiod_to_irq(ltc3651_charger->acpr_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request acpr irq\n"); + } + if (ltc3651_charger->fault_gpio) { + ret = gpiod_to_irq(ltc3651_charger->fault_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request fault irq\n"); + } + if (ltc3651_charger->chrg_gpio) { + ret = gpiod_to_irq(ltc3651_charger->chrg_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request chrg irq\n"); + } + + platform_set_drvdata(pdev, ltc3651_charger); + + return 0; +} + +static const struct of_device_id ltc3651_charger_match[] = { + { .compatible = "lltc,ltc3651-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3651_charger_match); + +static struct platform_driver ltc3651_charger_driver = { + .probe = ltc3651_charger_probe, + .driver = { + .name = "ltc3651-charger", + .of_match_table = ltc3651_charger_match, + }, +}; + +module_platform_driver(ltc3651_charger_driver); + +MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>"); +MODULE_DESCRIPTION("Driver for LTC3651 charger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ltc3651-charger"); diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 7ec7c7c202bd..540d3e0aa011 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -17,6 +17,7 @@ #include <linux/device.h> #include <linux/notifier.h> #include <linux/err.h> +#include <linux/of.h> #include <linux/power_supply.h> #include <linux/thermal.h> #include "power_supply.h" @@ -274,8 +275,30 @@ static int power_supply_check_supplies(struct power_supply *psy) return power_supply_populate_supplied_from(psy); } #else -static inline int power_supply_check_supplies(struct power_supply *psy) +static int power_supply_check_supplies(struct power_supply *psy) { + int nval, ret; + + if (!psy->dev.parent) + return 0; + + nval = device_property_read_string_array(psy->dev.parent, + "supplied-from", NULL, 0); + if (nval <= 0) + return 0; + + psy->supplied_from = devm_kmalloc_array(&psy->dev, nval, + sizeof(char *), GFP_KERNEL); + if (!psy->supplied_from) + return -ENOMEM; + + ret = device_property_read_string_array(psy->dev.parent, + "supplied-from", (const char **)psy->supplied_from, nval); + if (ret < 0) + return ret; + + psy->num_supplies = nval; + return 0; } #endif @@ -497,6 +520,62 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); #endif /* CONFIG_OF */ +int power_supply_get_battery_info(struct power_supply *psy, + struct power_supply_battery_info *info) +{ + struct device_node *battery_np; + const char *value; + int err; + + info->energy_full_design_uwh = -EINVAL; + info->charge_full_design_uah = -EINVAL; + info->voltage_min_design_uv = -EINVAL; + info->precharge_current_ua = -EINVAL; + info->charge_term_current_ua = -EINVAL; + info->constant_charge_current_max_ua = -EINVAL; + info->constant_charge_voltage_max_uv = -EINVAL; + + if (!psy->of_node) { + dev_warn(&psy->dev, "%s currently only supports devicetree\n", + __func__); + return -ENXIO; + } + + battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); + if (!battery_np) + return -ENODEV; + + err = of_property_read_string(battery_np, "compatible", &value); + if (err) + return err; + + if (strcmp("simple-battery", value)) + return -ENODEV; + + /* The property and field names below must correspond to elements + * in enum power_supply_property. For reasoning, see + * Documentation/power/power_supply_class.txt. + */ + + of_property_read_u32(battery_np, "energy-full-design-microwatt-hours", + &info->energy_full_design_uwh); + of_property_read_u32(battery_np, "charge-full-design-microamp-hours", + &info->charge_full_design_uah); + of_property_read_u32(battery_np, "voltage-min-design-microvolt", + &info->voltage_min_design_uv); + of_property_read_u32(battery_np, "precharge-current-microamp", + &info->precharge_current_ua); + of_property_read_u32(battery_np, "charge-term-current-microamp", + &info->charge_term_current_ua); + of_property_read_u32(battery_np, "constant_charge_current_max_microamp", + &info->constant_charge_current_max_ua); + of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt", + &info->constant_charge_voltage_max_uv); + + return 0; +} +EXPORT_SYMBOL_GPL(power_supply_get_battery_info); + int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -669,7 +748,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, return ret; } -static struct thermal_cooling_device_ops psy_tcd_ops = { +static const struct thermal_cooling_device_ops psy_tcd_ops = { .get_max_state = ps_get_max_charge_cntl_limit, .get_cur_state = ps_get_cur_chrage_cntl_limit, .set_cur_state = ps_set_cur_charge_cntl_limit, diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index bcde8d13476a..5204f115970f 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -40,35 +40,42 @@ static struct device_attribute power_supply_attrs[]; +static const char * const power_supply_type_text[] = { + "Unknown", "Battery", "UPS", "Mains", "USB", + "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", + "USB_PD", "USB_PD_DRP", "BrickID" +}; + +static const char * const power_supply_status_text[] = { + "Unknown", "Charging", "Discharging", "Not charging", "Full" +}; + +static const char * const power_supply_charge_type_text[] = { + "Unknown", "N/A", "Trickle", "Fast" +}; + +static const char * const power_supply_health_text[] = { + "Unknown", "Good", "Overheat", "Dead", "Over voltage", + "Unspecified failure", "Cold", "Watchdog timer expire", + "Safety timer expire" +}; + +static const char * const power_supply_technology_text[] = { + "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", + "LiMn" +}; + +static const char * const power_supply_capacity_level_text[] = { + "Unknown", "Critical", "Low", "Normal", "High", "Full" +}; + +static const char * const power_supply_scope_text[] = { + "Unknown", "System", "Device" +}; + static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { - static char *type_text[] = { - "Unknown", "Battery", "UPS", "Mains", "USB", - "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", - "USB_PD", "USB_PD_DRP" - }; - static char *status_text[] = { - "Unknown", "Charging", "Discharging", "Not charging", "Full" - }; - static char *charge_type[] = { - "Unknown", "N/A", "Trickle", "Fast" - }; - static char *health_text[] = { - "Unknown", "Good", "Overheat", "Dead", "Over voltage", - "Unspecified failure", "Cold", "Watchdog timer expire", - "Safety timer expire" - }; - static char *technology_text[] = { - "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", - "LiMn" - }; - static char *capacity_level_text[] = { - "Unknown", "Critical", "Low", "Normal", "High", "Full" - }; - static char *scope_text[] = { - "Unknown", "System", "Device" - }; ssize_t ret = 0; struct power_supply *psy = dev_get_drvdata(dev); const ptrdiff_t off = attr - power_supply_attrs; @@ -91,19 +98,26 @@ static ssize_t power_supply_show_property(struct device *dev, } if (off == POWER_SUPPLY_PROP_STATUS) - return sprintf(buf, "%s\n", status_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_status_text[value.intval]); else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) - return sprintf(buf, "%s\n", charge_type[value.intval]); + return sprintf(buf, "%s\n", + power_supply_charge_type_text[value.intval]); else if (off == POWER_SUPPLY_PROP_HEALTH) - return sprintf(buf, "%s\n", health_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_health_text[value.intval]); else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) - return sprintf(buf, "%s\n", technology_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_technology_text[value.intval]); else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) - return sprintf(buf, "%s\n", capacity_level_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_capacity_level_text[value.intval]); else if (off == POWER_SUPPLY_PROP_TYPE) - return sprintf(buf, "%s\n", type_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_type_text[value.intval]); else if (off == POWER_SUPPLY_PROP_SCOPE) - return sprintf(buf, "%s\n", scope_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_scope_text[value.intval]); else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) return sprintf(buf, "%s\n", value.strval); @@ -117,14 +131,46 @@ static ssize_t power_supply_store_property(struct device *dev, struct power_supply *psy = dev_get_drvdata(dev); const ptrdiff_t off = attr - power_supply_attrs; union power_supply_propval value; - long long_val; - /* TODO: support other types than int */ - ret = kstrtol(buf, 10, &long_val); - if (ret < 0) - return ret; + /* maybe it is a enum property? */ + switch (off) { + case POWER_SUPPLY_PROP_STATUS: + ret = sysfs_match_string(power_supply_status_text, buf); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = sysfs_match_string(power_supply_charge_type_text, buf); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = sysfs_match_string(power_supply_health_text, buf); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = sysfs_match_string(power_supply_technology_text, buf); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = sysfs_match_string(power_supply_capacity_level_text, buf); + break; + case POWER_SUPPLY_PROP_SCOPE: + ret = sysfs_match_string(power_supply_scope_text, buf); + break; + default: + ret = -EINVAL; + } + + /* + * If no match was found, then check to see if it is an integer. + * Integer values are valid for enums in addition to the text value. + */ + if (ret < 0) { + long long_val; + + ret = kstrtol(buf, 10, &long_val); + if (ret < 0) + return ret; + + ret = long_val; + } - value.intval = long_val; + value.intval = ret; ret = power_supply_set_property(psy, off, &value); if (ret < 0) @@ -196,6 +242,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), POWER_SUPPLY_ATTR(scope), + POWER_SUPPLY_ATTR(precharge_current), POWER_SUPPLY_ATTR(charge_term_current), POWER_SUPPLY_ATTR(calibrate), /* Properties of type `const char *' */ diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index e3a114e60f1a..f7059459f0fb 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -171,6 +171,7 @@ struct sbs_info { u32 i2c_retry_count; u32 poll_retry_count; struct delayed_work work; + struct mutex mode_lock; }; static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; @@ -199,7 +200,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) return ret; } - return le16_to_cpu(ret); + return ret; } static int sbs_read_string_data(struct i2c_client *client, u8 address, @@ -265,7 +266,7 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, memcpy(values, block_buffer + 1, block_length); values[block_length] = '\0'; - return le16_to_cpu(ret); + return ret; } static int sbs_write_word_data(struct i2c_client *client, u8 address, @@ -278,8 +279,7 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address, retries = chip->i2c_retry_count; while (retries > 0) { - ret = i2c_smbus_write_word_data(client, address, - le16_to_cpu(value)); + ret = i2c_smbus_write_word_data(client, address, value); if (ret >= 0) break; retries--; @@ -438,6 +438,11 @@ static int sbs_get_battery_property(struct i2c_client *client, } else { if (psp == POWER_SUPPLY_PROP_STATUS) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else if (psp == POWER_SUPPLY_PROP_CAPACITY) + /* sbs spec says that this can be >100 % + * even if max value is 100 % + */ + val->intval = min(ret, 100); else val->intval = 0; } @@ -548,12 +553,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client, if (ret < 0) return ret; - if (psp == POWER_SUPPLY_PROP_CAPACITY) { - /* sbs spec says that this can be >100 % - * even if max value is 100 % */ - val->intval = min(ret, 100); - } else - val->intval = ret; + val->intval = ret; ret = sbs_set_battery_mode(client, mode); if (ret < 0) @@ -618,12 +618,17 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - case POWER_SUPPLY_PROP_CAPACITY: ret = sbs_get_property_index(client, psp); if (ret < 0) break; + /* sbs_get_battery_capacity() will change the battery mode + * temporarily to read the requested attribute. Ensure we stay + * in the desired mode for the duration of the attribute read. + */ + mutex_lock(&chip->mode_lock); ret = sbs_get_battery_capacity(client, ret, psp, val); + mutex_unlock(&chip->mode_lock); break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: @@ -640,6 +645,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_CAPACITY: ret = sbs_get_property_index(client, psp); if (ret < 0) break; @@ -808,6 +814,7 @@ static int sbs_probe(struct i2c_client *client, psy_cfg.of_node = client->dev.of_node; psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + mutex_init(&chip->mode_lock); /* use pdata if available, fall back to DT properties, * or hardcoded defaults if not diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 2f82d0e9ec1b..3de802f169a1 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -153,7 +153,7 @@ struct twl4030_bci { }; /* strings for 'usb_mode' values */ -static char *modes[] = { "off", "auto", "continuous" }; +static const char *modes[] = { "off", "auto", "continuous" }; /* * clear and set bits on an given register on a given module @@ -624,63 +624,6 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) return IRQ_HANDLED; } -/* - * Provide "max_current" attribute in sysfs. - */ -static ssize_t -twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t n) -{ - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - int cur = 0; - int status = 0; - status = kstrtoint(buf, 10, &cur); - if (status) - return status; - if (cur < 0) - return -EINVAL; - if (dev == &bci->ac->dev) - bci->ac_cur = cur; - else - bci->usb_cur_target = cur; - - twl4030_charger_update_current(bci); - return n; -} - -/* - * sysfs max_current show - */ -static ssize_t twl4030_bci_max_current_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = 0; - int cur = -1; - u8 bcictl1; - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - - if (dev == &bci->ac->dev) { - if (!bci->ac_is_active) - cur = bci->ac_cur; - } else { - if (bci->ac_is_active) - cur = bci->usb_cur_target; - } - if (cur < 0) { - cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); - if (cur < 0) - return cur; - status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (status < 0) - return status; - cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN); - } - return scnprintf(buf, PAGE_SIZE, "%u\n", cur); -} - -static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show, - twl4030_bci_max_current_store); - static void twl4030_bci_usb_work(struct work_struct *data) { struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); @@ -726,14 +669,10 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, int mode; int status; - if (sysfs_streq(buf, modes[0])) - mode = 0; - else if (sysfs_streq(buf, modes[1])) - mode = 1; - else if (sysfs_streq(buf, modes[2])) - mode = 2; - else - return -EINVAL; + mode = sysfs_match_string(modes, buf); + if (mode < 0) + return mode; + if (dev == &bci->ac->dev) { if (mode == 2) return -EINVAL; @@ -1041,6 +980,12 @@ static int twl4030_bci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bci); + bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); + if (IS_ERR(bci->channel_vac)) { + bci->channel_vac = NULL; + dev_warn(&pdev->dev, "could not request vac iio channel"); + } + bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, NULL); if (IS_ERR(bci->ac)) { @@ -1074,12 +1019,6 @@ static int twl4030_bci_probe(struct platform_device *pdev) return ret; } - bci->channel_vac = iio_channel_get(&pdev->dev, "vac"); - if (IS_ERR(bci->channel_vac)) { - bci->channel_vac = NULL; - dev_warn(&pdev->dev, "could not request vac iio channel"); - } - INIT_WORK(&bci->work, twl4030_bci_usb_work); INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); @@ -1101,7 +1040,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) TWL4030_INTERRUPTS_BCIIMR1A); if (ret < 0) { dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - goto fail; + return ret; } reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); @@ -1111,14 +1050,10 @@ static int twl4030_bci_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); twl4030_charger_update_current(bci); - if (device_create_file(&bci->usb->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); if (device_create_file(&bci->usb->dev, &dev_attr_mode)) dev_warn(&pdev->dev, "could not create sysfs file\n"); if (device_create_file(&bci->ac->dev, &dev_attr_mode)) dev_warn(&pdev->dev, "could not create sysfs file\n"); - if (device_create_file(&bci->ac->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); twl4030_charger_enable_ac(bci, true); if (!IS_ERR_OR_NULL(bci->transceiver)) @@ -1134,10 +1069,6 @@ static int twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_backup(0, 0); return 0; -fail: - iio_channel_release(bci->channel_vac); - - return ret; } static int twl4030_bci_remove(struct platform_device *pdev) @@ -1148,11 +1079,7 @@ static int twl4030_bci_remove(struct platform_device *pdev) twl4030_charger_enable_usb(bci, false); twl4030_charger_enable_backup(0, 0); - iio_channel_release(bci->channel_vac); - - device_remove_file(&bci->usb->dev, &dev_attr_max_current); device_remove_file(&bci->usb->dev, &dev_attr_mode); - device_remove_file(&bci->ac->dev, &dev_attr_max_current); device_remove_file(&bci->ac->dev, &dev_attr_mode); /* mask interrupts */ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, |