1
0
Fork 0

power supply and reset changes for the v4.13 series

* 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
 * Misc. fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAllbV3gACgkQ2O7X88g7
 +pq5bw/8CfLQpgTgMzsehhb1EW03Bj5X7tmJKyb5w19Zmo1gwa1JMu9O30p4tAJa
 pid3LsQDBkcVCSn3QWFtVPe37Q3C1YgiatvdmPiaIbk5l62WTdPrBxKzFJCqPvke
 1Cb8bSpRi+EQ73DFVNuMZjAHl/eNwsS1/hiDQNBOPBnv3bZDtoSmYS3+7f6alSpD
 yFLdqSUr6RbP5gxesjaws5COgWMtxBzu8nDOtPaeT52Uxr6QW3T1RPmelj3paPHm
 kkd2z18q6k5vqHmMSoisNxaguYyKSvfTZBL/pwkC3MgtcZWcOBLchCg1vwrmTDkv
 sBzpVtgVNHTE/T0pEu4cbYmMdI6qsDiNeM0GxDAe/iOSKqrRFCEWWNbitBRTKthK
 XWd++er+wRJ6P9wx6t74Bb1AmneHWXAyB/hY8ma9LKpZpAV1NwCyJmAzWFCVCoz5
 eG9ZcakW/VXF0ROjD3IxRUMYZOrOuomeZEyOxxELls4y6MJuLEafn7Kyh0yBzx4D
 bxbvmHxhHxJPjnPvloJjD1vi/QVbsG1U54EJqLZlcc+wlYj/I2kNMu8emiu6khUi
 q7kEvRSY5t0M9J44MyNaDYutt69OgXeuT6MDNkAY24l6lQsv8MQVeKwz5Cwk/Noq
 hfpIjBwcibjvKWbkDAuF/wnVIjwYdXWLVRBU6Jop5GQK9lNZN6A=
 =SsiM
 -----END PGP SIGNATURE-----

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
  ...
hifive-unleashed-5.1
Linus Torvalds 2017-07-04 14:25:14 -07:00
commit a897a10141
31 changed files with 2204 additions and 233 deletions

View File

@ -1,20 +1,3 @@
What: /sys/class/power_supply/twl4030_ac/max_current
/sys/class/power_supply/twl4030_usb/max_current
Description:
Read/Write limit on current which may
be drawn from the ac (Accessory Charger) or
USB port.
Value is in micro-Amps.
Value is set automatically to an appropriate
value when a cable is plugged or unplugged.
Value can the set by writing to the attribute.
The change will only persist until the next
plug event. These event are reported via udev.
What: /sys/class/power_supply/twl4030_usb/mode
Description:
Changing mode for USB port.

View File

@ -0,0 +1,57 @@
Battery Characteristics
The devicetree battery node provides static battery characteristics.
In smart batteries, these are typically stored in non-volatile memory
on a fuel gauge chip. The battery node should be used where there is
no appropriate non-volatile memory, or it is unprogrammed/incorrect.
Upstream dts files should not include battery nodes, unless the battery
represented cannot easily be replaced in the system by one of a
different type. This prevents unpredictable, potentially harmful,
behavior should a replacement that changes the battery type occur
without a corresponding update to the dtb.
Required Properties:
- compatible: Must be "simple-battery"
Optional Properties:
- voltage-min-design-microvolt: drained battery voltage
- energy-full-design-microwatt-hours: battery design energy
- charge-full-design-microamp-hours: battery design capacity
- precharge-current-microamp: current for pre-charge phase
- charge-term-current-microamp: current for charge termination phase
- constant-charge-current-max-microamp: maximum constant input current
- constant-charge-voltage-max-microvolt: maximum constant input voltage
Battery properties are named, where possible, for the corresponding
elements in enum power_supply_property, defined in
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/power_supply.h
Batteries must be referenced by chargers and/or fuel-gauges
using a phandle. The phandle's property should be named
"monitored-battery".
Example:
bat: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3200000>;
energy-full-design-microwatt-hours = <5290000>;
charge-full-design-microamp-hours = <1430000>;
precharge-current-microamp = <256000>;
charge-term-current-microamp = <128000>;
constant-charge-current-max-microamp = <900000>;
constant-charge-voltage-max-microvolt = <4200000>;
};
charger: charger@11 {
....
monitored-battery = <&bat>;
...
};
fuel_gauge: fuel-gauge@22 {
....
monitored-battery = <&bat>;
...
};

View File

@ -1,7 +1,7 @@
Binding for TI BQ27XXX fuel gauge family
TI BQ27XXX fuel gauge family
Required properties:
- compatible: Should contain one of the following:
- compatible: contains one of the following:
* "ti,bq27200" - BQ27200
* "ti,bq27210" - BQ27210
* "ti,bq27500" - deprecated, use revision specific property below
@ -26,11 +26,28 @@ Required properties:
* "ti,bq27425" - BQ27425
* "ti,bq27441" - BQ27441
* "ti,bq27621" - BQ27621
- reg: integer, i2c address of the device.
- reg: integer, I2C address of the fuel gauge.
Optional properties:
- monitored-battery: phandle of battery characteristics node
The fuel gauge uses the following battery properties:
+ energy-full-design-microwatt-hours
+ charge-full-design-microamp-hours
+ voltage-min-design-microvolt
Both or neither of the *-full-design-*-hours properties must be set.
See Documentation/devicetree/bindings/power/supply/battery.txt
Example:
bq27510g3 {
compatible = "ti,bq27510g3";
reg = <0x55>;
};
bat: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3200000>;
energy-full-design-microwatt-hours = <5290000>;
charge-full-design-microamp-hours = <1430000>;
};
bq27510g3: fuel-gauge@55 {
compatible = "ti,bq27510g3";
reg = <0x55>;
monitored-battery = <&bat>;
};

View File

@ -0,0 +1,31 @@
Motorola CPCAP PMIC battery driver binding
Required properties:
- compatible: Shall be "motorola,cpcap-battery"
- interrupts: Interrupt specifier for each name in interrupt-names
- interrupt-names: Should contain the following entries:
"lowbph", "lowbpl", "chrgcurr1", "battdetb"
- io-channels: IIO ADC channel specifier for each name in io-channel-names
- io-channel-names: Should contain the following entries:
"battdetb", "battp", "chg_isense", "batti"
- power-supplies: List of phandles for power-supplying devices, as
described in power_supply.txt. Typically a reference
to cpcap_charger.
Example:
cpcap_battery: battery {
compatible = "motorola,cpcap-battery";
interrupts-extended = <
&cpcap 5 0 &cpcap 3 0
&cpcap 20 0 &cpcap 54 0
>;
interrupt-names =
"lowbph", "lowbpl",
"chrgcurr1", "battdetb";
io-channels = <&cpcap_adc 0 &cpcap_adc 1
&cpcap_adc 5 &cpcap_adc 6>;
io-channel-names = "battdetb", "battp",
"chg_isense", "batti";
power-supplies = <&cpcap_charger>;
};

View File

@ -0,0 +1,27 @@
ltc3651-charger
Required properties:
- compatible: "lltc,ltc3651-charger"
- lltc,acpr-gpios: Connect to ACPR output. See remark below.
Optional properties:
- lltc,fault-gpios: Connect to FAULT output. See remark below.
- lltc,chrg-gpios: Connect to CHRG output. See remark below.
The ltc3651 outputs are open-drain type and active low. The driver assumes the
GPIO reports "active" when the output is asserted, so if the pins have been
connected directly, the GPIO flags should be set to active low also.
The driver will attempt to aquire interrupts for all GPIOs to detect changes in
line state. If the system is not capabale of providing interrupts, the driver
cannot report changes and userspace will need to periodically read the sysfs
attributes to detect changes.
Example:
charger: battery-charger {
compatible = "lltc,ltc3651-charger";
lltc,acpr-gpios = <&gpio0 68 GPIO_ACTIVE_LOW>;
lltc,fault-gpios = <&gpio0 64 GPIO_ACTIVE_LOW>;
lltc,chrg-gpios = <&gpio0 63 GPIO_ACTIVE_LOW>;
};

View File

@ -25,8 +25,10 @@ Distance
Electricity
----------------------------------------
-microamp : micro amps
-microamp-hours : micro amp-hours
-ohms : Ohms
-micro-ohms : micro Ohms
-microwatt-hours: micro Watt-hours
-microvolt : micro volts
Temperature

View File

@ -115,28 +115,33 @@ of charge when battery became full/empty". It also could mean "value of
charge when battery considered full/empty at given conditions (temperature,
age)". I.e. these attributes represents real thresholds, not design values.
ENERGY_FULL, ENERGY_EMPTY - same as above but for energy.
CHARGE_COUNTER - the current charge counter (in µAh). This could easily
be negative; there is no empty or full value. It is only useful for
relative, time-based measurements.
PRECHARGE_CURRENT - the maximum charge current during precharge phase
of charge cycle (typically 20% of battery capacity).
CHARGE_TERM_CURRENT - Charge termination current. The charge cycle
terminates when battery voltage is above recharge threshold, and charge
current is below this setting (typically 10% of battery capacity).
CONSTANT_CHARGE_CURRENT - constant charge current programmed by charger.
CONSTANT_CHARGE_CURRENT_MAX - maximum charge current supported by the
power supply object.
INPUT_CURRENT_LIMIT - input current limit programmed by charger. Indicates
the current drawn from a charging source.
CHARGE_TERM_CURRENT - Charge termination current used to detect the end of charge
condition.
CALIBRATE - battery or coulomb counter calibration status
CONSTANT_CHARGE_VOLTAGE - constant charge voltage programmed by charger.
CONSTANT_CHARGE_VOLTAGE_MAX - maximum charge voltage supported by the
power supply object.
INPUT_CURRENT_LIMIT - input current limit programmed by charger. Indicates
the current drawn from a charging source.
CHARGE_CONTROL_LIMIT - current charge control limit setting
CHARGE_CONTROL_LIMIT_MAX - maximum charge control limit setting
ENERGY_FULL, ENERGY_EMPTY - same as above but for energy.
CALIBRATE - battery or coulomb counter calibration status
CAPACITY - capacity in percents.
CAPACITY_ALERT_MIN - minimum capacity alert value in percents.
@ -174,6 +179,18 @@ issued by external power supply will notify supplicants via
external_power_changed callback.
Devicetree battery characteristics
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Drivers should call power_supply_get_battery_info() to obtain battery
characteristics from a devicetree battery node, defined in
Documentation/devicetree/bindings/power/supply/battery.txt. This is
implemented in drivers/power/supply/bq27xxx_battery.c.
Properties in struct power_supply_battery_info and their counterparts in the
battery node have names corresponding to elements in enum power_supply_property,
for naming consistency between sysfs attributes and battery node properties.
QA
~~
Q: Where is POWER_SUPPLY_PROP_XYZ attribute?

View File

@ -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.

View File

@ -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)

View File

@ -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,

View File

@ -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-"

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -60,6 +60,8 @@ struct axp20x_batt_ps {
struct iio_channel *batt_chrg_i;
struct iio_channel *batt_dischrg_i;
struct iio_channel *batt_v;
/* Maximum constant charge current */
unsigned int max_ccc;
u8 axp_id;
};
@ -129,6 +131,14 @@ static void raw_to_constant_charge_current(struct axp20x_batt_ps *axp, int *val)
*val = *val * 150000 + 300000;
}
static void constant_charge_current_to_raw(struct axp20x_batt_ps *axp, int *val)
{
if (axp->axp_id == AXP209_ID)
*val = (*val - 300000) / 100000;
else
*val = (*val - 300000) / 150000;
}
static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp,
int *val)
{
@ -221,9 +231,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = AXP20X_CHRG_CTRL1_TGT_CURR;
raw_to_constant_charge_current(axp20x_batt, &val->intval);
val->intval = axp20x_batt->max_ccc;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
@ -340,10 +348,10 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt,
static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
int charge_current)
{
if (axp_batt->axp_id == AXP209_ID)
charge_current = (charge_current - 300000) / 100000;
else
charge_current = (charge_current - 300000) / 150000;
if (charge_current > axp_batt->max_ccc)
return -EINVAL;
constant_charge_current_to_raw(axp_batt, &charge_current);
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
return -EINVAL;
@ -352,6 +360,36 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
AXP20X_CHRG_CTRL1_TGT_CURR, charge_current);
}
static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp,
int charge_current)
{
bool lower_max = false;
constant_charge_current_to_raw(axp, &charge_current);
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
return -EINVAL;
raw_to_constant_charge_current(axp, &charge_current);
if (charge_current > axp->max_ccc)
dev_warn(axp->dev,
"Setting max constant charge current higher than previously defined. Note that increasing the constant charge current may damage your battery.\n");
else
lower_max = true;
axp->max_ccc = charge_current;
if (lower_max) {
int current_cc;
axp20x_get_constant_charge_current(axp, &current_cc);
if (current_cc > charge_current)
axp20x_set_constant_charge_current(axp, charge_current);
}
return 0;
}
static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt,
int min_voltage)
{
@ -380,6 +418,9 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return axp20x_set_constant_charge_current(axp20x_batt,
val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return axp20x_set_max_constant_charge_current(axp20x_batt,
val->intval);
default:
return -EINVAL;
@ -405,7 +446,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
{
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT;
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
}
static const struct power_supply_desc axp20x_batt_ps_desc = {
@ -433,6 +475,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
{
struct axp20x_batt_ps *axp20x_batt;
struct power_supply_config psy_cfg = {};
struct power_supply_battery_info info;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
@ -484,6 +527,35 @@ static int axp20x_power_probe(struct platform_device *pdev)
return PTR_ERR(axp20x_batt->batt);
}
if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
int vmin = info.voltage_min_design_uv;
int ccc = info.constant_charge_current_max_ua;
if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
vmin))
dev_err(&pdev->dev,
"couldn't set voltage_min_design\n");
/* Set max to unverified value to be able to set CCC */
axp20x_batt->max_ccc = ccc;
if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt,
ccc)) {
dev_err(&pdev->dev,
"couldn't set constant charge current from DT: fallback to minimum value\n");
ccc = 300000;
axp20x_batt->max_ccc = ccc;
axp20x_set_constant_charge_current(axp20x_batt, ccc);
}
}
/*
* Update max CCC to a valid value if battery info is present or set it
* to current register value by default.
*/
axp20x_get_constant_charge_current(axp20x_batt,
&axp20x_batt->max_ccc);
return 0;
}

View File

@ -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;

View File

@ -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,

View File

@ -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;
return di->bus.read(di, di->regs[reg_index], single);
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;
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);

View File

@ -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)

View File

@ -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");

View File

@ -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",

View File

@ -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 = &ltc3651_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");

View File

@ -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,

View File

@ -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;
}
value.intval = long_val;
/*
* 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 = 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 *' */

View File

@ -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

View File

@ -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,

View File

@ -40,6 +40,9 @@ struct bq27xxx_platform_data {
struct bq27xxx_device_info;
struct bq27xxx_access_methods {
int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single);
int (*write)(struct bq27xxx_device_info *di, u8 reg, int value, bool single);
int (*read_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
int (*write_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len);
};
struct bq27xxx_reg_cache {
@ -60,7 +63,10 @@ struct bq27xxx_device_info {
struct device *dev;
int id;
enum bq27xxx_chip chip;
bool ram_chip;
const char *name;
struct bq27xxx_dm_reg *dm_regs;
u32 unseal_key;
struct bq27xxx_access_methods bus;
struct bq27xxx_reg_cache cache;
int charge_design_full;

View File

@ -146,6 +146,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_CALIBRATE,
/* Properties of type `const char *' */
@ -159,13 +160,14 @@ enum power_supply_type {
POWER_SUPPLY_TYPE_BATTERY,
POWER_SUPPLY_TYPE_UPS,
POWER_SUPPLY_TYPE_MAINS,
POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */
POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */
POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */
POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */
POWER_SUPPLY_TYPE_USB_TYPE_C, /* Type C Port */
POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */
POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */
POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */
POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */
POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */
POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */
POWER_SUPPLY_TYPE_USB_TYPE_C, /* Type C Port */
POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */
POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */
POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */
};
enum power_supply_notifier_events {
@ -288,6 +290,25 @@ struct power_supply_info {
int use_for_apm;
};
/*
* This is the recommended struct to manage static battery parameters,
* populated by power_supply_get_battery_info(). Most platform drivers should
* use these for consistency.
* Its field names must correspond to elements in enum power_supply_property.
* The default field value is -EINVAL.
* Power supply class itself doesn't use this.
*/
struct power_supply_battery_info {
int energy_full_design_uwh; /* microWatt-hours */
int charge_full_design_uah; /* microAmp-hours */
int voltage_min_design_uv; /* microVolts */
int precharge_current_ua; /* microAmps */
int charge_term_current_ua; /* microAmps */
int constant_charge_current_max_ua; /* microAmps */
int constant_charge_voltage_max_uv; /* microVolts */
};
extern struct atomic_notifier_head power_supply_notifier;
extern int power_supply_reg_notifier(struct notifier_block *nb);
extern void power_supply_unreg_notifier(struct notifier_block *nb);
@ -306,6 +327,9 @@ static inline struct power_supply *
devm_power_supply_get_by_phandle(struct device *dev, const char *property)
{ return NULL; }
#endif /* CONFIG_OF */
extern int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info *info);
extern void power_supply_changed(struct power_supply *psy);
extern int power_supply_am_i_supplied(struct power_supply *psy);
extern int power_supply_set_battery_charged(struct power_supply *psy);
@ -359,6 +383,8 @@ static inline bool power_supply_is_amp_property(enum power_supply_property psp)
case POWER_SUPPLY_PROP_CHARGE_NOW:
case POWER_SUPPLY_PROP_CHARGE_AVG:
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CURRENT_MAX: