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 ...
This commit is contained in:
commit
a897a10141
|
@ -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.
|
||||
|
|
|
@ -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>;
|
||||
...
|
||||
};
|
|
@ -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 {
|
||||
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>;
|
||||
};
|
||||
|
|
|
@ -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>;
|
||||
};
|
|
@ -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>;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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-"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,6 +60,8 @@ struct axp20x_batt_ps {
|
|||
struct iio_channel *batt_chrg_i;
|
||||
struct iio_channel *batt_dischrg_i;
|
||||
struct iio_channel *batt_v;
|
||||
/* Maximum constant charge current */
|
||||
unsigned int max_ccc;
|
||||
u8 axp_id;
|
||||
};
|
||||
|
||||
|
@ -129,6 +131,14 @@ static void raw_to_constant_charge_current(struct axp20x_batt_ps *axp, int *val)
|
|||
*val = *val * 150000 + 300000;
|
||||
}
|
||||
|
||||
static void constant_charge_current_to_raw(struct axp20x_batt_ps *axp, int *val)
|
||||
{
|
||||
if (axp->axp_id == AXP209_ID)
|
||||
*val = (*val - 300000) / 100000;
|
||||
else
|
||||
*val = (*val - 300000) / 150000;
|
||||
}
|
||||
|
||||
static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp,
|
||||
int *val)
|
||||
{
|
||||
|
@ -221,9 +231,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
|
|||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = AXP20X_CHRG_CTRL1_TGT_CURR;
|
||||
raw_to_constant_charge_current(axp20x_batt, &val->intval);
|
||||
|
||||
val->intval = axp20x_batt->max_ccc;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
|
@ -340,10 +348,10 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt,
|
|||
static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
|
||||
int charge_current)
|
||||
{
|
||||
if (axp_batt->axp_id == AXP209_ID)
|
||||
charge_current = (charge_current - 300000) / 100000;
|
||||
else
|
||||
charge_current = (charge_current - 300000) / 150000;
|
||||
if (charge_current > axp_batt->max_ccc)
|
||||
return -EINVAL;
|
||||
|
||||
constant_charge_current_to_raw(axp_batt, &charge_current);
|
||||
|
||||
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
|
||||
return -EINVAL;
|
||||
|
@ -352,6 +360,36 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
|
|||
AXP20X_CHRG_CTRL1_TGT_CURR, charge_current);
|
||||
}
|
||||
|
||||
static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp,
|
||||
int charge_current)
|
||||
{
|
||||
bool lower_max = false;
|
||||
|
||||
constant_charge_current_to_raw(axp, &charge_current);
|
||||
|
||||
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
|
||||
return -EINVAL;
|
||||
|
||||
raw_to_constant_charge_current(axp, &charge_current);
|
||||
|
||||
if (charge_current > axp->max_ccc)
|
||||
dev_warn(axp->dev,
|
||||
"Setting max constant charge current higher than previously defined. Note that increasing the constant charge current may damage your battery.\n");
|
||||
else
|
||||
lower_max = true;
|
||||
|
||||
axp->max_ccc = charge_current;
|
||||
|
||||
if (lower_max) {
|
||||
int current_cc;
|
||||
|
||||
axp20x_get_constant_charge_current(axp, ¤t_cc);
|
||||
if (current_cc > charge_current)
|
||||
axp20x_set_constant_charge_current(axp, charge_current);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt,
|
||||
int min_voltage)
|
||||
{
|
||||
|
@ -380,6 +418,9 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
return axp20x_set_constant_charge_current(axp20x_batt,
|
||||
val->intval);
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
return axp20x_set_max_constant_charge_current(axp20x_batt,
|
||||
val->intval);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
@ -405,7 +446,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
|
|||
{
|
||||
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
|
||||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
|
||||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT;
|
||||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
|
||||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
|
||||
}
|
||||
|
||||
static const struct power_supply_desc axp20x_batt_ps_desc = {
|
||||
|
@ -433,6 +475,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct axp20x_batt_ps *axp20x_batt;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct power_supply_battery_info info;
|
||||
|
||||
if (!of_device_is_available(pdev->dev.of_node))
|
||||
return -ENODEV;
|
||||
|
@ -484,6 +527,35 @@ static int axp20x_power_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(axp20x_batt->batt);
|
||||
}
|
||||
|
||||
if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
|
||||
int vmin = info.voltage_min_design_uv;
|
||||
int ccc = info.constant_charge_current_max_ua;
|
||||
|
||||
if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
|
||||
vmin))
|
||||
dev_err(&pdev->dev,
|
||||
"couldn't set voltage_min_design\n");
|
||||
|
||||
/* Set max to unverified value to be able to set CCC */
|
||||
axp20x_batt->max_ccc = ccc;
|
||||
|
||||
if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt,
|
||||
ccc)) {
|
||||
dev_err(&pdev->dev,
|
||||
"couldn't set constant charge current from DT: fallback to minimum value\n");
|
||||
ccc = 300000;
|
||||
axp20x_batt->max_ccc = ccc;
|
||||
axp20x_set_constant_charge_current(axp20x_batt, ccc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update max CCC to a valid value if battery info is present or set it
|
||||
* to current register value by default.
|
||||
*/
|
||||
axp20x_get_constant_charge_current(axp20x_batt,
|
||||
&axp20x_batt->max_ccc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright (C) 2017, Topic Embedded Products
|
||||
* Driver for LTC3651 charger IC.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
struct ltc3651_charger {
|
||||
struct power_supply *charger;
|
||||
struct power_supply_desc charger_desc;
|
||||
struct gpio_desc *acpr_gpio;
|
||||
struct gpio_desc *fault_gpio;
|
||||
struct gpio_desc *chrg_gpio;
|
||||
};
|
||||
|
||||
static irqreturn_t ltc3651_charger_irq(int irq, void *devid)
|
||||
{
|
||||
struct power_supply *charger = devid;
|
||||
|
||||
power_supply_changed(charger);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static inline struct ltc3651_charger *psy_to_ltc3651_charger(
|
||||
struct power_supply *psy)
|
||||
{
|
||||
return power_supply_get_drvdata(psy);
|
||||
}
|
||||
|
||||
static int ltc3651_charger_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp, union power_supply_propval *val)
|
||||
{
|
||||
struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!ltc3651_charger->chrg_gpio) {
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
if (gpiod_get_value(ltc3651_charger->chrg_gpio))
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
if (!ltc3651_charger->fault_gpio) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
if (!gpiod_get_value(ltc3651_charger->fault_gpio)) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* If the fault pin is active, the chrg pin explains the type
|
||||
* of failure.
|
||||
*/
|
||||
if (!ltc3651_charger->chrg_gpio) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
break;
|
||||
}
|
||||
val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ?
|
||||
POWER_SUPPLY_HEALTH_OVERHEAT :
|
||||
POWER_SUPPLY_HEALTH_DEAD;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum power_supply_property ltc3651_charger_properties[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
};
|
||||
|
||||
static int ltc3651_charger_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct ltc3651_charger *ltc3651_charger;
|
||||
struct power_supply_desc *charger_desc;
|
||||
int ret;
|
||||
|
||||
ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger),
|
||||
GFP_KERNEL);
|
||||
if (!ltc3651_charger)
|
||||
return -ENOMEM;
|
||||
|
||||
ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
|
||||
"lltc,acpr", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->acpr_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->acpr_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"lltc,fault", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->fault_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->fault_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"lltc,chrg", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->chrg_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->chrg_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
charger_desc = <c3651_charger->charger_desc;
|
||||
charger_desc->name = pdev->dev.of_node->name;
|
||||
charger_desc->type = POWER_SUPPLY_TYPE_MAINS;
|
||||
charger_desc->properties = ltc3651_charger_properties;
|
||||
charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties);
|
||||
charger_desc->get_property = ltc3651_charger_get_property;
|
||||
psy_cfg.of_node = pdev->dev.of_node;
|
||||
psy_cfg.drv_data = ltc3651_charger;
|
||||
|
||||
ltc3651_charger->charger = devm_power_supply_register(&pdev->dev,
|
||||
charger_desc, &psy_cfg);
|
||||
if (IS_ERR(ltc3651_charger->charger)) {
|
||||
ret = PTR_ERR(ltc3651_charger->charger);
|
||||
dev_err(&pdev->dev, "Failed to register power supply: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire IRQs for the GPIO pins if possible. If the system does not
|
||||
* support IRQs on these pins, userspace will have to poll the sysfs
|
||||
* files manually.
|
||||
*/
|
||||
if (ltc3651_charger->acpr_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->acpr_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request acpr irq\n");
|
||||
}
|
||||
if (ltc3651_charger->fault_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->fault_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request fault irq\n");
|
||||
}
|
||||
if (ltc3651_charger->chrg_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->chrg_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request chrg irq\n");
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ltc3651_charger);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ltc3651_charger_match[] = {
|
||||
{ .compatible = "lltc,ltc3651-charger" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ltc3651_charger_match);
|
||||
|
||||
static struct platform_driver ltc3651_charger_driver = {
|
||||
.probe = ltc3651_charger_probe,
|
||||
.driver = {
|
||||
.name = "ltc3651-charger",
|
||||
.of_match_table = ltc3651_charger_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ltc3651_charger_driver);
|
||||
|
||||
MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
|
||||
MODULE_DESCRIPTION("Driver for LTC3651 charger");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:ltc3651-charger");
|
|
@ -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,
|
||||
|
|
|
@ -40,35 +40,42 @@
|
|||
|
||||
static struct device_attribute power_supply_attrs[];
|
||||
|
||||
static ssize_t power_supply_show_property(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf) {
|
||||
static char *type_text[] = {
|
||||
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"
|
||||
"USB_PD", "USB_PD_DRP", "BrickID"
|
||||
};
|
||||
static char *status_text[] = {
|
||||
|
||||
static const char * const power_supply_status_text[] = {
|
||||
"Unknown", "Charging", "Discharging", "Not charging", "Full"
|
||||
};
|
||||
static char *charge_type[] = {
|
||||
|
||||
static const char * const power_supply_charge_type_text[] = {
|
||||
"Unknown", "N/A", "Trickle", "Fast"
|
||||
};
|
||||
static char *health_text[] = {
|
||||
|
||||
static const char * const power_supply_health_text[] = {
|
||||
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
|
||||
"Unspecified failure", "Cold", "Watchdog timer expire",
|
||||
"Safety timer expire"
|
||||
};
|
||||
static char *technology_text[] = {
|
||||
|
||||
static const char * const power_supply_technology_text[] = {
|
||||
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
|
||||
"LiMn"
|
||||
};
|
||||
static char *capacity_level_text[] = {
|
||||
|
||||
static const char * const power_supply_capacity_level_text[] = {
|
||||
"Unknown", "Critical", "Low", "Normal", "High", "Full"
|
||||
};
|
||||
static char *scope_text[] = {
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
/* maybe it is a enum property? */
|
||||
switch (off) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
ret = sysfs_match_string(power_supply_status_text, buf);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||||
ret = sysfs_match_string(power_supply_charge_type_text, buf);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
ret = sysfs_match_string(power_supply_health_text, buf);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
||||
ret = sysfs_match_string(power_supply_technology_text, buf);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||
ret = sysfs_match_string(power_supply_capacity_level_text, buf);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
ret = sysfs_match_string(power_supply_scope_text, buf);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If no match was found, then check to see if it is an integer.
|
||||
* Integer values are valid for enums in addition to the text value.
|
||||
*/
|
||||
if (ret < 0) {
|
||||
long long_val;
|
||||
|
||||
/* TODO: support other types than int */
|
||||
ret = kstrtol(buf, 10, &long_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
value.intval = long_val;
|
||||
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 *' */
|
||||
|
|
|
@ -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,11 +553,6 @@ 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;
|
||||
|
||||
ret = sbs_set_battery_mode(client, mode);
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 *' */
|
||||
|
@ -166,6 +167,7 @@ enum power_supply_type {
|
|||
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:
|
||||
|
|
Loading…
Reference in New Issue