power supply and reset changes for the v5.18 series
power-supply core: - Introduce "Bypass" charging type used by USB PPS standard - Refactor power_supply_set_input_current_limit_from_supplier() - Add fwnode support to power_supply_get_battery_info() Drivers: - ab8500: continue migrating towards using standard core APIs - axp288 fuel-gauge: refactor driver to be fully resource managed - battery-samsung-sdi: new in-kernel provider for (constant) Samsung battery info - bq24190: disable boost regulator on shutdown - bq24190: add support for battery-info on ACPI based systems - bq25890: prepare driver for usage on ACPI based systems - bq25890: add boost regulator support - cpcap-battery: add NVMEM based battery detection support - injoinic ip5xxx: new driver for power bank IC - upi ug3105: new battery driver - misc. small improvements and fixes -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmI4ub0ACgkQ2O7X88g7 +poBBg/+OTb2O+dJsigwT9q2HKz98oPSwd+Zd0L/s1hCtc4AhlsNPHimEOYslvOl WN3auTQO1rjB4gi73p9A5NKTiAMTnXuCXoY0pLRgkcKybHGzliDqPY99Em1VqLHT OdJ+xG1qdk34T5hLW6ZfHV1VlB1J6u/JZbXyGbBzZ2r1mP3sLpWCW87cRTuBpaXD vmdWBRhYdz47pJS0PQs4TI7nFWoCvLB6Hmqe79zTI4hgRkKS05XeU3HXu+npdtdS 2OmPKwVcHIeuI3blTvtn7pHiX1j4Le319mmlOtwtdeEr70ONng9p2L6GPhW88ewH 1AZG58V6hFInMKKVHBbftEHybZ7wEc9CmjQ+l6VVdwHAgWwQEijsY3aG+fQ41Yw/ x1IRMMv5SO0A2amZ+mMxmIs7aQ0OXe78n8DiDPnhSWdh7q7+KgzXlBawmWDEZ9LZ HUcfDOQ+EISfg9F81Q7KKVDsSClUk2zIVbs944Y/4emT78XQjPq/NPlLpfwy0kMC 46lIO5prhWqe8WXtqkyR7x04nkrzo9Q/yWuj959l+bYACAtns8zQNZ+f8O2TvJ7V Is2DoaT5fELUD2D8NOla5RYZAtPzkfDf3iaNOLG7mkzGuz1RPcn3mgl0+a760AiZ poNmjfaKlY0d1WHc8AoxZgoKZxDZ3ckjAt8YzI/QT0RXEvoBEtY= =OiYc -----END PGP SIGNATURE----- Merge tag 'for-v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply Pull power supply and reset updates from Sebastian Reichel: "Power-supply core: - Introduce "Bypass" charging type used by USB PPS standard - Refactor power_supply_set_input_current_limit_from_supplier() - Add fwnode support to power_supply_get_battery_info() Drivers: - ab8500: continue migrating towards using standard core APIs - axp288 fuel-gauge: refactor driver to be fully resource managed - battery-samsung-sdi: new in-kernel provider for (constant) Samsung battery info - bq24190: disable boost regulator on shutdown - bq24190: add support for battery-info on ACPI based systems - bq25890: prepare driver for usage on ACPI based systems - bq25890: add boost regulator support - cpcap-battery: add NVMEM based battery detection support - injoinic ip5xxx: new driver for power bank IC - upi ug3105: new battery driver - misc small improvements and fixes" * tag 'for-v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (94 commits) power: ab8500_chargalg: Use CLOCK_MONOTONIC power: supply: Add a driver for Injoinic power bank ICs dt-bindings: trivial-devices: Add Injoinic power bank ICs dt-bindings: vendor-prefixes: Add Injoinic power: supply: ab8500: Remove unused variable power: supply: da9150-fg: Remove unnecessary print function dev_err() power: supply: ab8500: fix a handful of spelling mistakes power: supply: ab8500_fg: Account for line impedance dt-bindings: power: supply: ab8500_fg: Add line impedance power: supply: axp20x_usb_power: fix platform_get_irq.cocci warnings power: supply: axp20x_ac_power: fix platform_get_irq.cocci warning power: supply: wm8350-power: Add missing free in free_charger_irq power: supply: wm8350-power: Handle error for wm8350_register_irq power: supply: Static data for Samsung batteries power: supply: ab8500_fg: Use VBAT-to-Ri if possible power: supply: Support VBAT-to-Ri lookup tables power: supply: ab8500: Standardize BTI resistance power: supply: ab8500: Standardize alert mode charging power: supply: ab8500: Standardize maintenance charging power: supply: bq24190_charger: Delay applying charge_type changes when OTG 5V Vbus boost is on ...
This commit is contained in:
commit
8eb48fc7c5
@ -380,13 +380,17 @@ Description:
|
||||
algorithm to adjust the charge rate dynamically, without
|
||||
any user configuration required. "Custom" means that the charger
|
||||
uses the charge_control_* properties as configuration for some
|
||||
different algorithm.
|
||||
different algorithm. "Long Life" means the charger reduces its
|
||||
charging rate in order to prolong the battery health. "Bypass"
|
||||
means the charger bypasses the charging path around the
|
||||
integrated converter allowing for a "smart" wall adaptor to
|
||||
perform the power conversion externally.
|
||||
|
||||
Access: Read, Write
|
||||
|
||||
Valid values:
|
||||
"Unknown", "N/A", "Trickle", "Fast", "Standard",
|
||||
"Adaptive", "Custom"
|
||||
"Adaptive", "Custom", "Long Life", "Bypass"
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_term_current
|
||||
Date: July 2014
|
||||
|
@ -25,6 +25,11 @@ properties:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
deprecated: true
|
||||
|
||||
line-impedance-micro-ohms:
|
||||
description: The line impedance between the battery and the
|
||||
AB8500 inputs, to compensate for this when determining internal
|
||||
resistance.
|
||||
|
||||
interrupts:
|
||||
maxItems: 5
|
||||
|
||||
|
@ -143,6 +143,14 @@ properties:
|
||||
- infineon,xdpe12254
|
||||
# Infineon Multi-phase Digital VR Controller xdpe12284
|
||||
- infineon,xdpe12284
|
||||
# Injoinic IP5108 2.0A Power Bank IC with I2C
|
||||
- injoinic,ip5108
|
||||
# Injoinic IP5109 2.1A Power Bank IC with I2C
|
||||
- injoinic,ip5109
|
||||
# Injoinic IP5207 1.2A Power Bank IC with I2C
|
||||
- injoinic,ip5207
|
||||
# Injoinic IP5209 2.4A Power Bank IC with I2C
|
||||
- injoinic,ip5209
|
||||
# Inspur Power System power supply unit version 1
|
||||
- inspur,ipsps1
|
||||
# Intersil ISL29028 Ambient Light and Proximity Sensor
|
||||
|
@ -571,6 +571,8 @@ patternProperties:
|
||||
description: InfoVision Optoelectronics Kunshan Co. Ltd.
|
||||
"^ingenic,.*":
|
||||
description: Ingenic Semiconductor
|
||||
"^injoinic,.*":
|
||||
description: Injoinic Technology Corp.
|
||||
"^innolux,.*":
|
||||
description: Innolux Corporation
|
||||
"^inside-secure,.*":
|
||||
|
@ -9537,6 +9537,11 @@ F: include/linux/mfd/ingenic-tcu.h
|
||||
F: sound/soc/codecs/jz47*
|
||||
F: sound/soc/jz4740/
|
||||
|
||||
INJOINIC IP5xxx POWER BANK IC DRIVER
|
||||
M: Samuel Holland <samuel@sholland.org>
|
||||
S: Maintained
|
||||
F: drivers/power/supply/ip5xxx_power.c
|
||||
|
||||
INOTIFY
|
||||
M: Jan Kara <jack@suse.cz>
|
||||
R: Amir Goldstein <amir73il@gmail.com>
|
||||
|
@ -61,6 +61,8 @@ config EXTCON_INTEL_INT3496
|
||||
config EXTCON_INTEL_CHT_WC
|
||||
tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
|
||||
depends on INTEL_SOC_PMIC_CHTWC
|
||||
depends on USB_SUPPORT
|
||||
select USB_ROLE_SWITCH
|
||||
help
|
||||
Say Y here to enable extcon support for charger detection / control
|
||||
on the Intel Cherrytrail Whiskey Cove PMIC.
|
||||
|
@ -14,8 +14,12 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/role.h>
|
||||
|
||||
#include "extcon-intel.h"
|
||||
|
||||
@ -101,8 +105,13 @@ struct cht_wc_extcon_data {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct extcon_dev *edev;
|
||||
struct usb_role_switch *role_sw;
|
||||
struct regulator *vbus_boost;
|
||||
struct power_supply *psy;
|
||||
enum power_supply_usb_type usb_type;
|
||||
unsigned int previous_cable;
|
||||
bool usb_host;
|
||||
bool vbus_boost_enabled;
|
||||
};
|
||||
|
||||
static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
|
||||
@ -112,13 +121,21 @@ static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
|
||||
return INTEL_USB_ID_GND;
|
||||
case CHT_WC_PWRSRC_RID_FLOAT:
|
||||
return INTEL_USB_ID_FLOAT;
|
||||
/*
|
||||
* According to the spec. we should read the USB-ID pin ADC value here
|
||||
* to determine the resistance of the used pull-down resister and then
|
||||
* return RID_A / RID_B / RID_C based on this. But all "Accessory
|
||||
* Charger Adapter"s (ACAs) which users can actually buy always use
|
||||
* a combination of a charging port with one or more USB-A ports, so
|
||||
* they should always use a resistor indicating RID_A. But the spec
|
||||
* is hard to read / badly-worded so some of them actually indicate
|
||||
* they are a RID_B ACA evnen though they clearly are a RID_A ACA.
|
||||
* To workaround this simply always return INTEL_USB_RID_A, which
|
||||
* matches all the ACAs which users can actually buy.
|
||||
*/
|
||||
case CHT_WC_PWRSRC_RID_ACA:
|
||||
return INTEL_USB_RID_A;
|
||||
default:
|
||||
/*
|
||||
* Once we have IIO support for the GPADC we should read
|
||||
* the USBID GPADC channel here and determine ACA role
|
||||
* based on that.
|
||||
*/
|
||||
return INTEL_USB_ID_FLOAT;
|
||||
}
|
||||
}
|
||||
@ -147,14 +164,15 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
if (status != CHT_WC_USBSRC_STS_SUCCESS) {
|
||||
if (ignore_errors)
|
||||
return EXTCON_CHG_USB_SDP; /* Save fallback */
|
||||
if (!ignore_errors) {
|
||||
if (status == CHT_WC_USBSRC_STS_FAIL)
|
||||
dev_warn(ext->dev, "Could not detect charger type\n");
|
||||
else
|
||||
dev_warn(ext->dev, "Timeout detecting charger type\n");
|
||||
}
|
||||
|
||||
if (status == CHT_WC_USBSRC_STS_FAIL)
|
||||
dev_warn(ext->dev, "Could not detect charger type\n");
|
||||
else
|
||||
dev_warn(ext->dev, "Timeout detecting charger type\n");
|
||||
return EXTCON_CHG_USB_SDP; /* Save fallback */
|
||||
/* Safe fallback */
|
||||
usbsrc = CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT;
|
||||
}
|
||||
|
||||
usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
|
||||
@ -163,18 +181,23 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
|
||||
dev_warn(ext->dev,
|
||||
"Unhandled charger type %d, defaulting to SDP\n",
|
||||
ret);
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
||||
return EXTCON_CHG_USB_SDP;
|
||||
case CHT_WC_USBSRC_TYPE_SDP:
|
||||
case CHT_WC_USBSRC_TYPE_FLOATING:
|
||||
case CHT_WC_USBSRC_TYPE_OTHER:
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
||||
return EXTCON_CHG_USB_SDP;
|
||||
case CHT_WC_USBSRC_TYPE_CDP:
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
|
||||
return EXTCON_CHG_USB_CDP;
|
||||
case CHT_WC_USBSRC_TYPE_DCP:
|
||||
case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
|
||||
case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
||||
return EXTCON_CHG_USB_DCP;
|
||||
case CHT_WC_USBSRC_TYPE_ACA:
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
|
||||
return EXTCON_CHG_USB_ACA;
|
||||
}
|
||||
}
|
||||
@ -216,6 +239,18 @@ static void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext,
|
||||
CHT_WC_CHGRCTRL1_OTGMODE, val);
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret);
|
||||
|
||||
if (ext->vbus_boost && ext->vbus_boost_enabled != enable) {
|
||||
if (enable)
|
||||
ret = regulator_enable(ext->vbus_boost);
|
||||
else
|
||||
ret = regulator_disable(ext->vbus_boost);
|
||||
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error updating Vbus boost regulator: %d\n", ret);
|
||||
else
|
||||
ext->vbus_boost_enabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
static void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext,
|
||||
@ -245,6 +280,9 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
|
||||
unsigned int cable = EXTCON_NONE;
|
||||
/* Ignore errors in host mode, as the 5v boost converter is on then */
|
||||
bool ignore_get_charger_errors = ext->usb_host;
|
||||
enum usb_role role;
|
||||
|
||||
ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
|
||||
ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
|
||||
if (ret) {
|
||||
@ -288,6 +326,21 @@ set_state:
|
||||
|
||||
ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A));
|
||||
extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host);
|
||||
|
||||
if (ext->usb_host)
|
||||
role = USB_ROLE_HOST;
|
||||
else if (pwrsrc_sts & CHT_WC_PWRSRC_VBUS)
|
||||
role = USB_ROLE_DEVICE;
|
||||
else
|
||||
role = USB_ROLE_NONE;
|
||||
|
||||
/* Note: this is a no-op when ext->role_sw is NULL */
|
||||
ret = usb_role_switch_set_role(ext->role_sw, role);
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error setting USB-role: %d\n", ret);
|
||||
|
||||
if (ext->psy)
|
||||
power_supply_changed(ext->psy);
|
||||
}
|
||||
|
||||
static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
|
||||
@ -333,6 +386,114 @@ static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_find_role_sw(struct cht_wc_extcon_data *ext)
|
||||
{
|
||||
const struct software_node *swnode;
|
||||
struct fwnode_handle *fwnode;
|
||||
|
||||
swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
|
||||
if (!swnode)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
fwnode = software_node_fwnode(swnode);
|
||||
ext->role_sw = usb_role_switch_find_by_fwnode(fwnode);
|
||||
fwnode_handle_put(fwnode);
|
||||
|
||||
return ext->role_sw ? 0 : -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
static void cht_wc_extcon_put_role_sw(void *data)
|
||||
{
|
||||
struct cht_wc_extcon_data *ext = data;
|
||||
|
||||
usb_role_switch_put(ext->role_sw);
|
||||
}
|
||||
|
||||
/* Some boards require controlling the role-sw and Vbus based on the id-pin */
|
||||
static int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cht_wc_extcon_find_role_sw(ext);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* On x86/ACPI platforms the regulator <-> consumer link is provided
|
||||
* by platform_data passed to the regulator driver. This means that
|
||||
* this info is not available before the regulator driver has bound.
|
||||
* Use devm_regulator_get_optional() to avoid getting a dummy
|
||||
* regulator and wait for the regulator to show up if necessary.
|
||||
*/
|
||||
ext->vbus_boost = devm_regulator_get_optional(ext->dev, "vbus");
|
||||
if (IS_ERR(ext->vbus_boost)) {
|
||||
ret = PTR_ERR(ext->vbus_boost);
|
||||
if (ret == -ENODEV)
|
||||
ret = -EPROBE_DEFER;
|
||||
|
||||
return dev_err_probe(ext->dev, ret, "getting Vbus regulator");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_psy_get_prop(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct cht_wc_extcon_data *ext = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
val->intval = ext->usb_type;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = ext->usb_type ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = {
|
||||
POWER_SUPPLY_USB_TYPE_SDP,
|
||||
POWER_SUPPLY_USB_TYPE_CDP,
|
||||
POWER_SUPPLY_USB_TYPE_DCP,
|
||||
POWER_SUPPLY_USB_TYPE_ACA,
|
||||
POWER_SUPPLY_USB_TYPE_UNKNOWN,
|
||||
};
|
||||
|
||||
static const enum power_supply_property cht_wc_extcon_psy_props[] = {
|
||||
POWER_SUPPLY_PROP_USB_TYPE,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc cht_wc_extcon_psy_desc = {
|
||||
.name = "cht_wcove_pwrsrc",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.usb_types = cht_wc_extcon_psy_usb_types,
|
||||
.num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types),
|
||||
.properties = cht_wc_extcon_psy_props,
|
||||
.num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props),
|
||||
.get_property = cht_wc_extcon_psy_get_prop,
|
||||
};
|
||||
|
||||
static int cht_wc_extcon_register_psy(struct cht_wc_extcon_data *ext)
|
||||
{
|
||||
struct power_supply_config psy_cfg = { .drv_data = ext };
|
||||
|
||||
ext->psy = devm_power_supply_register(ext->dev,
|
||||
&cht_wc_extcon_psy_desc,
|
||||
&psy_cfg);
|
||||
return PTR_ERR_OR_ZERO(ext->psy);
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
|
||||
@ -358,20 +519,47 @@ static int cht_wc_extcon_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(ext->edev))
|
||||
return PTR_ERR(ext->edev);
|
||||
|
||||
/*
|
||||
* When a host-cable is detected the BIOS enables an external 5v boost
|
||||
* converter to power connected devices there are 2 problems with this:
|
||||
* 1) This gets seen by the external battery charger as a valid Vbus
|
||||
* supply and it then tries to feed Vsys from this creating a
|
||||
* feedback loop which causes aprox. 300 mA extra battery drain
|
||||
* (and unless we drive the external-charger-disable pin high it
|
||||
* also tries to charge the battery causing even more feedback).
|
||||
* 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply
|
||||
* Since the external battery charger has its own 5v boost converter
|
||||
* which does not have these issues, we simply turn the separate
|
||||
* external 5v boost converter off and leave it off entirely.
|
||||
*/
|
||||
cht_wc_extcon_set_5v_boost(ext, false);
|
||||
switch (pmic->cht_wc_model) {
|
||||
case INTEL_CHT_WC_GPD_WIN_POCKET:
|
||||
/*
|
||||
* When a host-cable is detected the BIOS enables an external 5v boost
|
||||
* converter to power connected devices there are 2 problems with this:
|
||||
* 1) This gets seen by the external battery charger as a valid Vbus
|
||||
* supply and it then tries to feed Vsys from this creating a
|
||||
* feedback loop which causes aprox. 300 mA extra battery drain
|
||||
* (and unless we drive the external-charger-disable pin high it
|
||||
* also tries to charge the battery causing even more feedback).
|
||||
* 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply
|
||||
* Since the external battery charger has its own 5v boost converter
|
||||
* which does not have these issues, we simply turn the separate
|
||||
* external 5v boost converter off and leave it off entirely.
|
||||
*/
|
||||
cht_wc_extcon_set_5v_boost(ext, false);
|
||||
break;
|
||||
case INTEL_CHT_WC_LENOVO_YOGABOOK1:
|
||||
/* Do this first, as it may very well return -EPROBE_DEFER. */
|
||||
ret = cht_wc_extcon_get_role_sw_and_regulator(ext);
|
||||
if (ret)
|
||||
return ret;
|
||||
/*
|
||||
* The bq25890 used here relies on this driver's BC-1.2 charger
|
||||
* detection, and the bq25890 driver expect this info to be
|
||||
* available through a parent power_supply class device which
|
||||
* models the detected charger (idem to how the Type-C TCPM code
|
||||
* registers a power_supply classdev for the connected charger).
|
||||
*/
|
||||
ret = cht_wc_extcon_register_psy(ext);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case INTEL_CHT_WC_XIAOMI_MIPAD2:
|
||||
ret = cht_wc_extcon_get_role_sw_and_regulator(ext);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Enable sw control */
|
||||
ret = cht_wc_extcon_sw_control(ext, true);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power/bq24190_charger.h>
|
||||
#include <linux/power/bq25890_charger.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define CHT_WC_I2C_CTRL 0x5e24
|
||||
@ -270,6 +271,7 @@ static const struct irq_chip cht_wc_i2c_irq_chip = {
|
||||
.name = "cht_wc_ext_chrg_irq_chip",
|
||||
};
|
||||
|
||||
/********** GPD Win / Pocket charger IC settings **********/
|
||||
static const char * const bq24190_suppliers[] = {
|
||||
"tcpm-source-psy-i2c-fusb302" };
|
||||
|
||||
@ -304,17 +306,92 @@ static struct bq24190_platform_data bq24190_pdata = {
|
||||
.regulator_init_data = &bq24190_vbus_init_data,
|
||||
};
|
||||
|
||||
static struct i2c_board_info gpd_win_board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
};
|
||||
|
||||
/********** Xiaomi Mi Pad 2 charger IC settings **********/
|
||||
static struct regulator_consumer_supply bq2589x_vbus_consumer = {
|
||||
.supply = "vbus",
|
||||
.dev_name = "cht_wcove_pwrsrc",
|
||||
};
|
||||
|
||||
static const struct regulator_init_data bq2589x_vbus_init_data = {
|
||||
.constraints = {
|
||||
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
|
||||
},
|
||||
.consumer_supplies = &bq2589x_vbus_consumer,
|
||||
.num_consumer_supplies = 1,
|
||||
};
|
||||
|
||||
static struct bq25890_platform_data bq2589x_pdata = {
|
||||
.regulator_init_data = &bq2589x_vbus_init_data,
|
||||
};
|
||||
|
||||
static const struct property_entry xiaomi_mipad2_props[] = {
|
||||
PROPERTY_ENTRY_BOOL("linux,skip-reset"),
|
||||
PROPERTY_ENTRY_BOOL("linux,read-back-settings"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node xiaomi_mipad2_node = {
|
||||
.properties = xiaomi_mipad2_props,
|
||||
};
|
||||
|
||||
static struct i2c_board_info xiaomi_mipad2_board_info = {
|
||||
.type = "bq25890",
|
||||
.addr = 0x6a,
|
||||
.dev_name = "bq25890",
|
||||
.swnode = &xiaomi_mipad2_node,
|
||||
.platform_data = &bq2589x_pdata,
|
||||
};
|
||||
|
||||
/********** Lenovo Yogabook YB1-X90F/-X91F/-X91L charger settings **********/
|
||||
static const char * const lenovo_yb1_bq25892_suppliers[] = { "cht_wcove_pwrsrc" };
|
||||
|
||||
static const struct property_entry lenovo_yb1_bq25892_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from",
|
||||
lenovo_yb1_bq25892_suppliers),
|
||||
PROPERTY_ENTRY_U32("linux,pump-express-vbus-max", 12000000),
|
||||
PROPERTY_ENTRY_BOOL("linux,skip-reset"),
|
||||
/*
|
||||
* The firmware sets everything to the defaults, which leads to a
|
||||
* somewhat low charge-current of 2048mA and worse to a battery-voltage
|
||||
* of 4.2V instead of 4.35V (when booted without a charger connected).
|
||||
* Use our own values instead of "linux,read-back-settings" to fix this.
|
||||
*/
|
||||
PROPERTY_ENTRY_U32("ti,charge-current", 4224000),
|
||||
PROPERTY_ENTRY_U32("ti,battery-regulation-voltage", 4352000),
|
||||
PROPERTY_ENTRY_U32("ti,termination-current", 256000),
|
||||
PROPERTY_ENTRY_U32("ti,precharge-current", 128000),
|
||||
PROPERTY_ENTRY_U32("ti,minimum-sys-voltage", 3500000),
|
||||
PROPERTY_ENTRY_U32("ti,boost-voltage", 4998000),
|
||||
PROPERTY_ENTRY_U32("ti,boost-max-current", 1400000),
|
||||
PROPERTY_ENTRY_BOOL("ti,use-ilim-pin"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node lenovo_yb1_bq25892_node = {
|
||||
.properties = lenovo_yb1_bq25892_props,
|
||||
};
|
||||
|
||||
static struct i2c_board_info lenovo_yogabook1_board_info = {
|
||||
.type = "bq25892",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq25892",
|
||||
.swnode = &lenovo_yb1_bq25892_node,
|
||||
.platform_data = &bq2589x_pdata,
|
||||
};
|
||||
|
||||
static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
|
||||
struct i2c_board_info *board_info = NULL;
|
||||
struct cht_wc_i2c_adap *adap;
|
||||
struct i2c_board_info board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
};
|
||||
int ret, reg, irq;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
@ -379,17 +456,24 @@ static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
goto remove_irq_domain;
|
||||
|
||||
/*
|
||||
* Normally the Whiskey Cove PMIC is paired with a TI bq24292i charger,
|
||||
* connected to this i2c bus, and a max17047 fuel-gauge and a fusb302
|
||||
* USB Type-C controller connected to another i2c bus. In this setup
|
||||
* the max17047 and fusb302 devices are enumerated through an INT33FE
|
||||
* ACPI device. If this device is present register an i2c-client for
|
||||
* the TI bq24292i charger.
|
||||
*/
|
||||
if (acpi_dev_present("INT33FE", NULL, -1)) {
|
||||
board_info.irq = adap->client_irq;
|
||||
adap->client = i2c_new_client_device(&adap->adapter, &board_info);
|
||||
switch (pmic->cht_wc_model) {
|
||||
case INTEL_CHT_WC_GPD_WIN_POCKET:
|
||||
board_info = &gpd_win_board_info;
|
||||
break;
|
||||
case INTEL_CHT_WC_XIAOMI_MIPAD2:
|
||||
board_info = &xiaomi_mipad2_board_info;
|
||||
break;
|
||||
case INTEL_CHT_WC_LENOVO_YOGABOOK1:
|
||||
board_info = &lenovo_yogabook1_board_info;
|
||||
break;
|
||||
default:
|
||||
dev_warn(&pdev->dev, "Unknown model, not instantiating charger device\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (board_info) {
|
||||
board_info->irq = adap->client_irq;
|
||||
adap->client = i2c_new_client_device(&adap->adapter, board_info);
|
||||
if (IS_ERR(adap->client)) {
|
||||
ret = PTR_ERR(adap->client);
|
||||
goto del_adapter;
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -134,9 +135,44 @@ static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
|
||||
.num_regs = 1,
|
||||
};
|
||||
|
||||
static const struct dmi_system_id cht_wc_model_dmi_ids[] = {
|
||||
{
|
||||
/* GPD win / GPD pocket mini laptops */
|
||||
.driver_data = (void *)(long)INTEL_CHT_WC_GPD_WIN_POCKET,
|
||||
/*
|
||||
* This DMI match may not seem unique, but it is. In the 67000+
|
||||
* DMI decode dumps from linux-hardware.org only 116 have
|
||||
* board_vendor set to "AMI Corporation" and of those 116 only
|
||||
* the GPD win's and pocket's board_name is "Default string".
|
||||
*/
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
|
||||
},
|
||||
}, {
|
||||
/* Xiaomi Mi Pad 2 */
|
||||
.driver_data = (void *)(long)INTEL_CHT_WC_XIAOMI_MIPAD2,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"),
|
||||
},
|
||||
}, {
|
||||
/* Lenovo Yoga Book X90F / X91F / X91L */
|
||||
.driver_data = (void *)(long)INTEL_CHT_WC_LENOVO_YOGABOOK1,
|
||||
.matches = {
|
||||
/* Non exact match to match all versions */
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static int cht_wc_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
const struct dmi_system_id *id;
|
||||
struct intel_soc_pmic *pmic;
|
||||
acpi_status status;
|
||||
unsigned long long hrv;
|
||||
@ -160,6 +196,10 @@ static int cht_wc_probe(struct i2c_client *client)
|
||||
if (!pmic)
|
||||
return -ENOMEM;
|
||||
|
||||
id = dmi_first_match(cht_wc_model_dmi_ids);
|
||||
if (id)
|
||||
pmic->cht_wc_model = (long)id->driver_data;
|
||||
|
||||
pmic->irq = client->irq;
|
||||
pmic->dev = dev;
|
||||
i2c_set_clientdata(client, pmic);
|
||||
|
@ -107,8 +107,8 @@ static int gemini_poweroff_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(gpw->base);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (!irq)
|
||||
return -EINVAL;
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
gpw->dev = dev;
|
||||
|
||||
|
@ -51,6 +51,14 @@ config GENERIC_ADC_BATTERY
|
||||
Say Y here to enable support for the generic battery driver
|
||||
which uses IIO framework to read adc.
|
||||
|
||||
config IP5XXX_POWER
|
||||
tristate "Injoinic IP5xxx power bank IC driver"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to include support for Injoinic IP5xxx power bank ICs,
|
||||
which include a battery charger and a boost converter.
|
||||
|
||||
config MAX8925_POWER
|
||||
tristate "MAX8925 battery charger support"
|
||||
depends on MFD_MAX8925
|
||||
@ -181,6 +189,12 @@ config BATTERY_OLPC
|
||||
help
|
||||
Say Y to enable support for the battery on the OLPC laptop.
|
||||
|
||||
config BATTERY_SAMSUNG_SDI
|
||||
bool "Samsung SDI batteries"
|
||||
help
|
||||
Say Y to enable support for Samsung SDI battery data.
|
||||
These batteries are used in Samsung mobile phones.
|
||||
|
||||
config BATTERY_TOSA
|
||||
tristate "Sharp SL-6000 (tosa) battery"
|
||||
depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX
|
||||
@ -351,14 +365,14 @@ config AXP20X_POWER
|
||||
|
||||
config AXP288_CHARGER
|
||||
tristate "X-Powers AXP288 Charger"
|
||||
depends on MFD_AXP20X && EXTCON_AXP288 && IOSF_MBI
|
||||
depends on MFD_AXP20X && EXTCON_AXP288 && IOSF_MBI && ACPI
|
||||
help
|
||||
Say yes here to have support X-Power AXP288 power management IC (PMIC)
|
||||
integrated charger.
|
||||
|
||||
config AXP288_FUEL_GAUGE
|
||||
tristate "X-Powers AXP288 Fuel Gauge"
|
||||
depends on MFD_AXP20X && IIO && IOSF_MBI
|
||||
depends on MFD_AXP20X && IIO && IOSF_MBI && ACPI
|
||||
help
|
||||
Say yes here to have support for X-Power power management IC (PMIC)
|
||||
Fuel Gauge. The device provides battery statistics and status
|
||||
@ -728,6 +742,8 @@ config BATTERY_GAUGE_LTC2941
|
||||
config AB8500_BM
|
||||
bool "AB8500 Battery Management Driver"
|
||||
depends on AB8500_CORE && AB8500_GPADC && (IIO = y) && OF
|
||||
select THERMAL
|
||||
select THERMAL_OF
|
||||
help
|
||||
Say Y to include support for AB8500 battery management.
|
||||
|
||||
@ -866,4 +882,19 @@ config CHARGER_SURFACE
|
||||
Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
||||
Surface Book 3, and Surface Laptop Go.
|
||||
|
||||
config BATTERY_UG3105
|
||||
tristate "uPI uG3105 battery monitor driver"
|
||||
depends on I2C
|
||||
help
|
||||
Battery monitor driver for the uPI uG3105 battery monitor.
|
||||
|
||||
Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead
|
||||
it is expected to be use in combination with some always on
|
||||
microcontroller reading its coulomb-counter before it can wrap
|
||||
(it must be read every 400 seconds!).
|
||||
|
||||
Since Linux does not monitor coulomb-counter changes while the
|
||||
device is off or suspended, the functionality of this driver is
|
||||
limited to reporting capacity only.
|
||||
|
||||
endif # POWER_SUPPLY
|
||||
|
@ -12,6 +12,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
|
||||
obj-$(CONFIG_PDA_POWER) += pda_power.o
|
||||
obj-$(CONFIG_APM_POWER) += apm_power.o
|
||||
obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o
|
||||
obj-$(CONFIG_IP5XXX_POWER) += ip5xxx_power.o
|
||||
obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
|
||||
obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
|
||||
obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
|
||||
@ -34,6 +35,7 @@ obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
|
||||
obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o
|
||||
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
|
||||
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
|
||||
obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
|
||||
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
|
||||
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
|
||||
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
|
||||
@ -105,3 +107,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
||||
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
|
||||
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
|
||||
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
|
||||
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
|
||||
|
@ -260,30 +260,6 @@ enum bup_vch_sel {
|
||||
#define BUS_PP_PRECHG_CURRENT_MASK 0x0E
|
||||
#define BUS_POWER_PATH_PRECHG_ENA 0x01
|
||||
|
||||
/*
|
||||
* ADC for the battery thermistor.
|
||||
* When using the AB8500_ADC_THERM_BATCTRL the battery ID resistor is combined
|
||||
* with a NTC resistor to both identify the battery and to measure its
|
||||
* temperature. Different phone manufactures uses different techniques to both
|
||||
* identify the battery and to read its temperature.
|
||||
*/
|
||||
enum ab8500_adc_therm {
|
||||
AB8500_ADC_THERM_BATCTRL,
|
||||
AB8500_ADC_THERM_BATTEMP,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ab8500_res_to_temp - defines one point in a temp to res curve. To
|
||||
* be used in battery packs that combines the identification resistor with a
|
||||
* NTC resistor.
|
||||
* @temp: battery pack temperature in Celsius
|
||||
* @resist: NTC resistor net total resistance
|
||||
*/
|
||||
struct ab8500_res_to_temp {
|
||||
int temp;
|
||||
int resist;
|
||||
};
|
||||
|
||||
/* Forward declaration */
|
||||
struct ab8500_fg;
|
||||
|
||||
@ -351,36 +327,6 @@ struct ab8500_maxim_parameters {
|
||||
int charger_curr_step_ua;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ab8500_battery_type - different batteries supported
|
||||
* @resis_high: battery upper resistance limit
|
||||
* @resis_low: battery lower resistance limit
|
||||
* @maint_a_cur_lvl: charger current in maintenance A state in mA
|
||||
* @maint_a_vol_lvl: charger voltage in maintenance A state in mV
|
||||
* @maint_a_chg_timer_h: charge time in maintenance A state
|
||||
* @maint_b_cur_lvl: charger current in maintenance B state in mA
|
||||
* @maint_b_vol_lvl: charger voltage in maintenance B state in mV
|
||||
* @maint_b_chg_timer_h: charge time in maintenance B state
|
||||
* @low_high_cur_lvl: charger current in temp low/high state in mA
|
||||
* @low_high_vol_lvl: charger voltage in temp low/high state in mV'
|
||||
* @n_r_t_tbl_elements: number of elements in r_to_t_tbl
|
||||
* @r_to_t_tbl: table containing resistance to temp points
|
||||
*/
|
||||
struct ab8500_battery_type {
|
||||
int resis_high;
|
||||
int resis_low;
|
||||
int maint_a_cur_lvl;
|
||||
int maint_a_vol_lvl;
|
||||
int maint_a_chg_timer_h;
|
||||
int maint_b_cur_lvl;
|
||||
int maint_b_vol_lvl;
|
||||
int maint_b_chg_timer_h;
|
||||
int low_high_cur_lvl;
|
||||
int low_high_vol_lvl;
|
||||
int n_temp_tbl_elements;
|
||||
const struct ab8500_res_to_temp *r_to_t_tbl;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ab8500_bm_capacity_levels - ab8500 capacity level data
|
||||
* @critical: critical capacity level in percent
|
||||
@ -421,9 +367,7 @@ struct ab8500_bm_charger_parameters {
|
||||
* @usb_safety_tmr_h safety timer for usb charger
|
||||
* @bkup_bat_v voltage which we charge the backup battery with
|
||||
* @bkup_bat_i current which we charge the backup battery with
|
||||
* @no_maintenance indicates that maintenance charging is disabled
|
||||
* @capacity_scaling indicates whether capacity scaling is to be used
|
||||
* @ab8500_adc_therm placement of thermistor, batctrl or battemp adc
|
||||
* @chg_unknown_bat flag to enable charging of unknown batteries
|
||||
* @enable_overshoot flag to enable VBAT overshoot control
|
||||
* @auto_trig flag to enable auto adc trigger
|
||||
@ -431,10 +375,8 @@ struct ab8500_bm_charger_parameters {
|
||||
* @interval_charging charge alg cycle period time when charging (sec)
|
||||
* @interval_not_charging charge alg cycle period time when not charging (sec)
|
||||
* @temp_hysteresis temperature hysteresis
|
||||
* @gnd_lift_resistance Battery ground to phone ground resistance (mOhm)
|
||||
* @maxi maximization parameters
|
||||
* @cap_levels capacity in percent for the different capacity levels
|
||||
* @bat_type table of supported battery types
|
||||
* @chg_params charger parameters
|
||||
* @fg_params fuel gauge parameters
|
||||
*/
|
||||
@ -447,41 +389,20 @@ struct ab8500_bm_data {
|
||||
int usb_safety_tmr_h;
|
||||
int bkup_bat_v;
|
||||
int bkup_bat_i;
|
||||
bool no_maintenance;
|
||||
bool capacity_scaling;
|
||||
bool chg_unknown_bat;
|
||||
bool enable_overshoot;
|
||||
bool auto_trig;
|
||||
enum ab8500_adc_therm adc_therm;
|
||||
int fg_res;
|
||||
int interval_charging;
|
||||
int interval_not_charging;
|
||||
int temp_hysteresis;
|
||||
int gnd_lift_resistance;
|
||||
const struct ab8500_maxim_parameters *maxi;
|
||||
const struct ab8500_bm_capacity_levels *cap_levels;
|
||||
struct ab8500_battery_type *bat_type;
|
||||
const struct ab8500_bm_charger_parameters *chg_params;
|
||||
const struct ab8500_fg_parameters *fg_params;
|
||||
};
|
||||
|
||||
enum {
|
||||
NTC_EXTERNAL = 0,
|
||||
NTC_INTERNAL,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct res_to_temp - defines one point in a temp to res curve. To
|
||||
* be used in battery packs that combines the identification resistor with a
|
||||
* NTC resistor.
|
||||
* @temp: battery pack temperature in Celsius
|
||||
* @resist: NTC resistor net total resistance
|
||||
*/
|
||||
struct res_to_temp {
|
||||
int temp;
|
||||
int resist;
|
||||
};
|
||||
|
||||
/* Forward declaration */
|
||||
struct ab8500_fg;
|
||||
|
||||
|
@ -43,28 +43,6 @@ static struct power_supply_battery_ocv_table ocv_cap_tbl[] = {
|
||||
{ .ocv = 3094000, .capacity = 0},
|
||||
};
|
||||
|
||||
/*
|
||||
* Note that the res_to_temp table must be strictly sorted by falling
|
||||
* resistance values to work.
|
||||
*/
|
||||
static const struct ab8500_res_to_temp temp_tbl[] = {
|
||||
{-5, 214834},
|
||||
{ 0, 162943},
|
||||
{ 5, 124820},
|
||||
{10, 96520},
|
||||
{15, 75306},
|
||||
{20, 59254},
|
||||
{25, 47000},
|
||||
{30, 37566},
|
||||
{35, 30245},
|
||||
{40, 24520},
|
||||
{45, 20010},
|
||||
{50, 16432},
|
||||
{55, 13576},
|
||||
{60, 11280},
|
||||
{65, 9425},
|
||||
};
|
||||
|
||||
/*
|
||||
* Note that the batres_vs_temp table must be strictly sorted by falling
|
||||
* temperature values to work. Factory resistance is 300 mOhm and the
|
||||
@ -80,20 +58,19 @@ static struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[]
|
||||
{ .temp = -20, .resistance = 198 /* 595 mOhm */ },
|
||||
};
|
||||
|
||||
/* Default battery type for reference designs is the unknown type */
|
||||
static struct ab8500_battery_type bat_type_thermistor_unknown = {
|
||||
.resis_high = 0,
|
||||
.resis_low = 0,
|
||||
.maint_a_cur_lvl = 400,
|
||||
.maint_a_vol_lvl = 4050,
|
||||
.maint_a_chg_timer_h = 60,
|
||||
.maint_b_cur_lvl = 400,
|
||||
.maint_b_vol_lvl = 4000,
|
||||
.maint_b_chg_timer_h = 200,
|
||||
.low_high_cur_lvl = 300,
|
||||
.low_high_vol_lvl = 4000,
|
||||
.n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
|
||||
.r_to_t_tbl = temp_tbl,
|
||||
static struct power_supply_maintenance_charge_table ab8500_maint_charg_table[] = {
|
||||
{
|
||||
/* Maintenance charging phase A, 60 hours */
|
||||
.charge_current_max_ua = 400000,
|
||||
.charge_voltage_max_uv = 4050000,
|
||||
.charge_safety_timer_minutes = 60*60,
|
||||
},
|
||||
{
|
||||
/* Maintenance charging phase B, 200 hours */
|
||||
.charge_current_max_ua = 400000,
|
||||
.charge_voltage_max_uv = 4000000,
|
||||
.charge_safety_timer_minutes = 200*60,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct ab8500_bm_capacity_levels cap_levels = {
|
||||
@ -148,17 +125,13 @@ struct ab8500_bm_data ab8500_bm_data = {
|
||||
.usb_safety_tmr_h = 4,
|
||||
.bkup_bat_v = BUP_VCH_SEL_2P6V,
|
||||
.bkup_bat_i = BUP_ICH_SEL_150UA,
|
||||
.no_maintenance = false,
|
||||
.capacity_scaling = false,
|
||||
.adc_therm = AB8500_ADC_THERM_BATCTRL,
|
||||
.chg_unknown_bat = false,
|
||||
.enable_overshoot = false,
|
||||
.fg_res = 100,
|
||||
.cap_levels = &cap_levels,
|
||||
.bat_type = &bat_type_thermistor_unknown,
|
||||
.interval_charging = 5,
|
||||
.interval_not_charging = 120,
|
||||
.gnd_lift_resistance = 34,
|
||||
.maxi = &ab8500_maxi_params,
|
||||
.chg_params = &chg,
|
||||
.fg_params = &fg,
|
||||
@ -188,13 +161,11 @@ int ab8500_bm_of_probe(struct power_supply *psy,
|
||||
* fall back to safe defaults.
|
||||
*/
|
||||
if ((bi->voltage_min_design_uv < 0) ||
|
||||
(bi->voltage_max_design_uv < 0) ||
|
||||
(bi->overvoltage_limit_uv < 0)) {
|
||||
(bi->voltage_max_design_uv < 0)) {
|
||||
/* Nominal voltage is 3.7V for unknown batteries */
|
||||
bi->voltage_min_design_uv = 3700000;
|
||||
bi->voltage_max_design_uv = 3700000;
|
||||
/* Termination voltage (overcharge limit) 4.05V */
|
||||
bi->overvoltage_limit_uv = 4050000;
|
||||
/* Termination voltage 4.05V */
|
||||
bi->voltage_max_design_uv = 4050000;
|
||||
}
|
||||
|
||||
if (bi->constant_charge_current_max_ua < 0)
|
||||
@ -207,6 +178,24 @@ int ab8500_bm_of_probe(struct power_supply *psy,
|
||||
/* Charging stops when we drop below this current */
|
||||
bi->charge_term_current_ua = 200000;
|
||||
|
||||
if (!bi->maintenance_charge || !bi->maintenance_charge_size) {
|
||||
bi->maintenance_charge = ab8500_maint_charg_table;
|
||||
bi->maintenance_charge_size = ARRAY_SIZE(ab8500_maint_charg_table);
|
||||
}
|
||||
|
||||
if (bi->alert_low_temp_charge_current_ua < 0 ||
|
||||
bi->alert_low_temp_charge_voltage_uv < 0)
|
||||
{
|
||||
bi->alert_low_temp_charge_current_ua = 300000;
|
||||
bi->alert_low_temp_charge_voltage_uv = 4000000;
|
||||
}
|
||||
if (bi->alert_high_temp_charge_current_ua < 0 ||
|
||||
bi->alert_high_temp_charge_voltage_uv < 0)
|
||||
{
|
||||
bi->alert_high_temp_charge_current_ua = 300000;
|
||||
bi->alert_high_temp_charge_voltage_uv = 4000000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal resistance and factory resistance are tightly coupled
|
||||
* so both MUST be defined or we fall back to defaults.
|
||||
@ -218,6 +207,13 @@ int ab8500_bm_of_probe(struct power_supply *psy,
|
||||
bi->resist_table_size = ARRAY_SIZE(temp_to_batres_tbl_thermistor);
|
||||
}
|
||||
|
||||
/* The default battery is emulated by a resistor at 7K */
|
||||
if (bi->bti_resistance_ohm < 0 ||
|
||||
bi->bti_resistance_tolerance < 0) {
|
||||
bi->bti_resistance_ohm = 7000;
|
||||
bi->bti_resistance_tolerance = 20;
|
||||
}
|
||||
|
||||
if (!bi->ocv_table[0]) {
|
||||
/* Default capacity table at say 25 degrees Celsius */
|
||||
bi->ocv_temp[0] = 25;
|
||||
|
@ -26,13 +26,12 @@
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/abx500.h>
|
||||
#include <linux/mfd/abx500/ab8500.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/fixp-arith.h>
|
||||
|
||||
#include "ab8500-bm.h"
|
||||
|
||||
#define VTVOUT_V 1800
|
||||
|
||||
#define BTEMP_THERMAL_LOW_LIMIT -10
|
||||
#define BTEMP_THERMAL_MED_LIMIT 0
|
||||
#define BTEMP_THERMAL_HIGH_LIMIT_52 52
|
||||
@ -82,7 +81,7 @@ struct ab8500_btemp_ranges {
|
||||
* @bat_temp: Dispatched battery temperature in degree Celsius
|
||||
* @prev_bat_temp Last measured battery temperature in degree Celsius
|
||||
* @parent: Pointer to the struct ab8500
|
||||
* @adc_btemp_ball: ADC channel for the battery ball temperature
|
||||
* @tz: Thermal zone for the battery
|
||||
* @adc_bat_ctrl: ADC channel for the battery control
|
||||
* @fg: Pointer to the struct fg
|
||||
* @bm: Platform specific battery management information
|
||||
@ -100,7 +99,7 @@ struct ab8500_btemp {
|
||||
int bat_temp;
|
||||
int prev_bat_temp;
|
||||
struct ab8500 *parent;
|
||||
struct iio_channel *btemp_ball;
|
||||
struct thermal_zone_device *tz;
|
||||
struct iio_channel *bat_ctrl;
|
||||
struct ab8500_fg *fg;
|
||||
struct ab8500_bm_data *bm;
|
||||
@ -135,8 +134,6 @@ static LIST_HEAD(ab8500_btemp_list);
|
||||
static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
|
||||
int v_batctrl, int inst_curr)
|
||||
{
|
||||
int rbs;
|
||||
|
||||
if (is_ab8500_1p1_or_earlier(di->parent)) {
|
||||
/*
|
||||
* For ABB cut1.0 and 1.1 BAT_CTRL is internally
|
||||
@ -145,23 +142,11 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
|
||||
return (450000 * (v_batctrl)) / (1800 - v_batctrl);
|
||||
}
|
||||
|
||||
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) {
|
||||
/*
|
||||
* If the battery has internal NTC, we use the current
|
||||
* source to calculate the resistance.
|
||||
*/
|
||||
rbs = (v_batctrl * 1000
|
||||
- di->bm->gnd_lift_resistance * inst_curr)
|
||||
/ di->curr_source;
|
||||
} else {
|
||||
/*
|
||||
* BAT_CTRL is internally
|
||||
* connected to 1.8V through a 80k resistor
|
||||
*/
|
||||
rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl);
|
||||
}
|
||||
|
||||
return rbs;
|
||||
/*
|
||||
* BAT_CTRL is internally
|
||||
* connected to 1.8V through a 80k resistor
|
||||
*/
|
||||
return (80000 * (v_batctrl)) / (1800 - v_batctrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,155 +171,6 @@ static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di)
|
||||
return vbtemp;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_btemp_curr_source_enable() - enable/disable batctrl current source
|
||||
* @di: pointer to the ab8500_btemp structure
|
||||
* @enable: enable or disable the current source
|
||||
*
|
||||
* Enable or disable the current sources for the BatCtrl AD channel
|
||||
*/
|
||||
static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
|
||||
bool enable)
|
||||
{
|
||||
int curr;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* BATCTRL current sources are included on AB8500 cut2.0
|
||||
* and future versions
|
||||
*/
|
||||
if (is_ab8500_1p1_or_earlier(di->parent))
|
||||
return 0;
|
||||
|
||||
/* Only do this for batteries with internal NTC */
|
||||
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && enable) {
|
||||
|
||||
if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
|
||||
curr = BAT_CTRL_7U_ENA;
|
||||
else
|
||||
curr = BAT_CTRL_20U_ENA;
|
||||
|
||||
dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
|
||||
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed setting cmp_force\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to wait one 32kHz cycle before enabling
|
||||
* the current source, since ForceBatCtrlCmpHigh needs
|
||||
* to be written in a separate cycle
|
||||
*/
|
||||
udelay(32);
|
||||
|
||||
ret = abx500_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
FORCE_BAT_CTRL_CMP_HIGH | curr);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed enabling current source\n",
|
||||
__func__);
|
||||
goto disable_curr_source;
|
||||
}
|
||||
} else if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && !enable) {
|
||||
dev_dbg(di->dev, "Disable BATCTRL curr source\n");
|
||||
|
||||
/* Write 0 to the curr bits */
|
||||
ret = abx500_mask_and_set_register_interruptible(
|
||||
di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
|
||||
~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
|
||||
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed disabling current source\n",
|
||||
__func__);
|
||||
goto disable_curr_source;
|
||||
}
|
||||
|
||||
/* Enable Pull-Up and comparator */
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
|
||||
BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed enabling PU and comp\n",
|
||||
__func__);
|
||||
goto enable_pu_comp;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to wait one 32kHz cycle before disabling
|
||||
* ForceBatCtrlCmpHigh since this needs to be written
|
||||
* in a separate cycle
|
||||
*/
|
||||
udelay(32);
|
||||
|
||||
/* Disable 'force comparator' */
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed disabling force comp\n",
|
||||
__func__);
|
||||
goto disable_force_comp;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time
|
||||
* if we got an error above
|
||||
*/
|
||||
disable_curr_source:
|
||||
/* Write 0 to the curr bits */
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
|
||||
~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
|
||||
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed disabling current source\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
enable_pu_comp:
|
||||
/* Enable Pull-Up and comparator */
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
|
||||
BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed enabling PU and comp\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
disable_force_comp:
|
||||
/*
|
||||
* We have to wait one 32kHz cycle before disabling
|
||||
* ForceBatCtrlCmpHigh since this needs to be written
|
||||
* in a separate cycle
|
||||
*/
|
||||
udelay(32);
|
||||
|
||||
/* Disable 'force comparator' */
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
|
||||
FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s failed disabling force comp\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_btemp_get_batctrl_res() - get battery resistance
|
||||
* @di: pointer to the ab8500_btemp structure
|
||||
@ -350,16 +186,6 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
|
||||
int inst_curr;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* BATCTRL current sources are included on AB8500 cut2.0
|
||||
* and future versions
|
||||
*/
|
||||
ret = ab8500_btemp_curr_source_enable(di, true);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s curr source enabled failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!di->fg)
|
||||
di->fg = ab8500_fg_get();
|
||||
if (!di->fg) {
|
||||
@ -395,107 +221,12 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
|
||||
|
||||
res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr);
|
||||
|
||||
ret = ab8500_btemp_curr_source_enable(di, false);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "%s curr source disable failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n",
|
||||
__func__, batctrl, res, inst_curr, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_btemp_res_to_temp() - resistance to temperature
|
||||
* @di: pointer to the ab8500_btemp structure
|
||||
* @tbl: pointer to the resiatance to temperature table
|
||||
* @tbl_size: size of the resistance to temperature table
|
||||
* @res: resistance to calculate the temperature from
|
||||
*
|
||||
* This function returns the battery temperature in degrees Celsius
|
||||
* based on the NTC resistance.
|
||||
*/
|
||||
static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
|
||||
const struct ab8500_res_to_temp *tbl, int tbl_size, int res)
|
||||
{
|
||||
int i;
|
||||
/*
|
||||
* Calculate the formula for the straight line
|
||||
* Simple interpolation if we are within
|
||||
* the resistance table limits, extrapolate
|
||||
* if resistance is outside the limits.
|
||||
*/
|
||||
if (res > tbl[0].resist)
|
||||
i = 0;
|
||||
else if (res <= tbl[tbl_size - 1].resist)
|
||||
i = tbl_size - 2;
|
||||
else {
|
||||
i = 0;
|
||||
while (!(res <= tbl[i].resist &&
|
||||
res > tbl[i + 1].resist))
|
||||
i++;
|
||||
}
|
||||
|
||||
return fixp_linear_interpolate(tbl[i].resist, tbl[i].temp,
|
||||
tbl[i + 1].resist, tbl[i + 1].temp,
|
||||
res);
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_btemp_measure_temp() - measure battery temperature
|
||||
* @di: pointer to the ab8500_btemp structure
|
||||
*
|
||||
* Returns battery temperature (on success) else the previous temperature
|
||||
*/
|
||||
static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
|
||||
{
|
||||
struct power_supply_battery_info *bi = di->bm->bi;
|
||||
int temp, ret;
|
||||
static int prev;
|
||||
int rbat, rntc, vntc;
|
||||
|
||||
if ((di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) &&
|
||||
(bi && (bi->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN))) {
|
||||
|
||||
rbat = ab8500_btemp_get_batctrl_res(di);
|
||||
if (rbat < 0) {
|
||||
dev_err(di->dev, "%s get batctrl res failed\n",
|
||||
__func__);
|
||||
/*
|
||||
* Return out-of-range temperature so that
|
||||
* charging is stopped
|
||||
*/
|
||||
return BTEMP_THERMAL_LOW_LIMIT;
|
||||
}
|
||||
|
||||
temp = ab8500_btemp_res_to_temp(di,
|
||||
di->bm->bat_type->r_to_t_tbl,
|
||||
di->bm->bat_type->n_temp_tbl_elements, rbat);
|
||||
} else {
|
||||
ret = iio_read_channel_processed(di->btemp_ball, &vntc);
|
||||
if (ret < 0) {
|
||||
dev_err(di->dev,
|
||||
"%s ADC conversion failed,"
|
||||
" using previous value\n", __func__);
|
||||
return prev;
|
||||
}
|
||||
/*
|
||||
* The PCB NTC is sourced from VTVOUT via a 230kOhm
|
||||
* resistor.
|
||||
*/
|
||||
rntc = 230000 * vntc / (VTVOUT_V - vntc);
|
||||
|
||||
temp = ab8500_btemp_res_to_temp(di,
|
||||
di->bm->bat_type->r_to_t_tbl,
|
||||
di->bm->bat_type->n_temp_tbl_elements, rntc);
|
||||
prev = temp;
|
||||
}
|
||||
dev_dbg(di->dev, "Battery temperature is %d\n", temp);
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_btemp_id() - Identify the connected battery
|
||||
* @di: pointer to the ab8500_btemp structure
|
||||
@ -506,8 +237,8 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
|
||||
*/
|
||||
static int ab8500_btemp_id(struct ab8500_btemp *di)
|
||||
{
|
||||
struct power_supply_battery_info *bi = di->bm->bi;
|
||||
int res;
|
||||
u8 i;
|
||||
|
||||
di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
|
||||
|
||||
@ -517,36 +248,17 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if ((res <= di->bm->bat_type->resis_high) &&
|
||||
(res >= di->bm->bat_type->resis_low)) {
|
||||
dev_info(di->dev, "Battery detected on %s"
|
||||
" low %d < res %d < high: %d"
|
||||
" index: %d\n",
|
||||
di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL ?
|
||||
"BATCTRL" : "BATTEMP",
|
||||
di->bm->bat_type->resis_low, res,
|
||||
di->bm->bat_type->resis_high, i);
|
||||
if (power_supply_battery_bti_in_range(bi, res)) {
|
||||
dev_info(di->dev, "Battery detected on BATCTRL (pin C3)"
|
||||
" resistance %d Ohm = %d Ohm +/- %d%%\n",
|
||||
res, bi->bti_resistance_ohm,
|
||||
bi->bti_resistance_tolerance);
|
||||
} else {
|
||||
dev_warn(di->dev, "Battery identified as unknown"
|
||||
", resistance %d Ohm\n", res);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only have to change current source if the
|
||||
* detected type is Type 1 (LIPO) resis_high = 53407, resis_low = 12500
|
||||
* if someone hacks this in.
|
||||
*
|
||||
* FIXME: make sure this is done automatically for the batteries
|
||||
* that need it.
|
||||
*/
|
||||
if ((di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) &&
|
||||
(di->bm->bi && (di->bm->bi->technology == POWER_SUPPLY_TECHNOLOGY_LIPO)) &&
|
||||
(res <= 53407) && (res >= 12500)) {
|
||||
dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
|
||||
di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -562,6 +274,9 @@ static void ab8500_btemp_periodic_work(struct work_struct *work)
|
||||
int bat_temp;
|
||||
struct ab8500_btemp *di = container_of(work,
|
||||
struct ab8500_btemp, btemp_periodic_work.work);
|
||||
/* Assume 25 degrees celsius as start temperature */
|
||||
static int prev = 25;
|
||||
int ret;
|
||||
|
||||
if (!di->initialized) {
|
||||
/* Identify the battery */
|
||||
@ -569,7 +284,17 @@ static void ab8500_btemp_periodic_work(struct work_struct *work)
|
||||
dev_warn(di->dev, "failed to identify the battery\n");
|
||||
}
|
||||
|
||||
bat_temp = ab8500_btemp_measure_temp(di);
|
||||
/* Failover if a reading is erroneous, use last meausurement */
|
||||
ret = thermal_zone_get_temp(di->tz, &bat_temp);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "error reading temperature\n");
|
||||
bat_temp = prev;
|
||||
} else {
|
||||
/* Convert from millicentigrades to centigrades */
|
||||
bat_temp /= 1000;
|
||||
prev = bat_temp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Filter battery temperature.
|
||||
* Allow direct updates on temperature only if two samples result in
|
||||
@ -998,12 +723,11 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
|
||||
di->dev = dev;
|
||||
di->parent = dev_get_drvdata(pdev->dev.parent);
|
||||
|
||||
/* Get ADC channels */
|
||||
di->btemp_ball = devm_iio_channel_get(dev, "btemp_ball");
|
||||
if (IS_ERR(di->btemp_ball)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(di->btemp_ball),
|
||||
"failed to get BTEMP BALL ADC channel\n");
|
||||
return ret;
|
||||
/* Get thermal zone and ADC */
|
||||
di->tz = thermal_zone_get_zone_by_name("battery-thermal");
|
||||
if (IS_ERR(di->tz)) {
|
||||
return dev_err_probe(dev, PTR_ERR(di->tz),
|
||||
"failed to get battery thermal zone\n");
|
||||
}
|
||||
di->bat_ctrl = devm_iio_channel_get(dev, "bat_ctrl");
|
||||
if (IS_ERR(di->bat_ctrl)) {
|
||||
|
@ -46,9 +46,6 @@
|
||||
/* Five minutes expressed in seconds */
|
||||
#define FIVE_MINUTES_IN_SECONDS 300
|
||||
|
||||
#define CHARGALG_CURR_STEP_LOW_UA 0
|
||||
#define CHARGALG_CURR_STEP_HIGH_UA 100000
|
||||
|
||||
/*
|
||||
* This is the battery capacity limit that will trigger a new
|
||||
* full charging cycle in the case where maintenance charging
|
||||
@ -80,17 +77,6 @@ struct ab8500_chargalg_charger_info {
|
||||
int ac_iset_ua;
|
||||
};
|
||||
|
||||
struct ab8500_chargalg_suspension_status {
|
||||
bool suspended_change;
|
||||
bool ac_suspended;
|
||||
bool usb_suspended;
|
||||
};
|
||||
|
||||
struct ab8500_chargalg_current_step_status {
|
||||
bool curr_step_change;
|
||||
int curr_step_ua;
|
||||
};
|
||||
|
||||
struct ab8500_chargalg_battery_data {
|
||||
int temp;
|
||||
int volt_uv;
|
||||
@ -118,8 +104,6 @@ enum ab8500_chargalg_states {
|
||||
STATE_TEMP_UNDEROVER,
|
||||
STATE_TEMP_LOWHIGH_INIT,
|
||||
STATE_TEMP_LOWHIGH,
|
||||
STATE_SUSPENDED_INIT,
|
||||
STATE_SUSPENDED,
|
||||
STATE_OVV_PROTECT_INIT,
|
||||
STATE_OVV_PROTECT,
|
||||
STATE_SAFETY_TIMER_EXPIRED_INIT,
|
||||
@ -149,8 +133,6 @@ static const char * const states[] = {
|
||||
"TEMP_UNDEROVER",
|
||||
"TEMP_LOWHIGH_INIT",
|
||||
"TEMP_LOWHIGH",
|
||||
"SUSPENDED_INIT",
|
||||
"SUSPENDED",
|
||||
"OVV_PROTECT_INIT",
|
||||
"OVV_PROTECT",
|
||||
"SAFETY_TIMER_EXPIRED_INIT",
|
||||
@ -167,7 +149,8 @@ struct ab8500_chargalg_events {
|
||||
bool batt_ovv;
|
||||
bool batt_rem;
|
||||
bool btemp_underover;
|
||||
bool btemp_lowhigh;
|
||||
bool btemp_low;
|
||||
bool btemp_high;
|
||||
bool main_thermal_prot;
|
||||
bool usb_thermal_prot;
|
||||
bool main_ovv;
|
||||
@ -186,8 +169,6 @@ struct ab8500_chargalg_events {
|
||||
* struct ab8500_charge_curr_maximization - Charger maximization parameters
|
||||
* @original_iset_ua: the non optimized/maximised charger current
|
||||
* @current_iset_ua: the charging current used at this moment
|
||||
* @test_delta_i_ua: the delta between the current we want to charge and the
|
||||
current that is really going into the battery
|
||||
* @condition_cnt: number of iterations needed before a new charger current
|
||||
is set
|
||||
* @max_current_ua: maximum charger current
|
||||
@ -200,7 +181,6 @@ struct ab8500_chargalg_events {
|
||||
struct ab8500_charge_curr_maximization {
|
||||
int original_iset_ua;
|
||||
int current_iset_ua;
|
||||
int test_delta_i_ua;
|
||||
int condition_cnt;
|
||||
int max_current_ua;
|
||||
int wait_cnt;
|
||||
@ -227,9 +207,7 @@ enum maxim_ret {
|
||||
* @ccm charging current maximization parameters
|
||||
* @chg_info: information about connected charger types
|
||||
* @batt_data: data of the battery
|
||||
* @susp_status: current charger suspension status
|
||||
* @bm: Platform specific battery management information
|
||||
* @curr_status: Current step status for over-current protection
|
||||
* @parent: pointer to the struct ab8500
|
||||
* @chargalg_psy: structure that holds the battery properties exposed by
|
||||
* the charging algorithm
|
||||
@ -253,9 +231,7 @@ struct ab8500_chargalg {
|
||||
struct ab8500_charge_curr_maximization ccm;
|
||||
struct ab8500_chargalg_charger_info chg_info;
|
||||
struct ab8500_chargalg_battery_data batt_data;
|
||||
struct ab8500_chargalg_suspension_status susp_status;
|
||||
struct ab8500 *parent;
|
||||
struct ab8500_chargalg_current_step_status curr_status;
|
||||
struct ab8500_bm_data *bm;
|
||||
struct power_supply *chargalg_psy;
|
||||
struct ux500_charger *ac_chg;
|
||||
@ -311,7 +287,7 @@ ab8500_chargalg_safety_timer_expired(struct hrtimer *timer)
|
||||
* the maintenance timer
|
||||
* @timer: pointer to the timer structure
|
||||
*
|
||||
* This function gets called when the maintenence timer
|
||||
* This function gets called when the maintenance timer
|
||||
* expires
|
||||
*/
|
||||
static enum hrtimer_restart
|
||||
@ -385,57 +361,28 @@ static int ab8500_chargalg_check_charger_enable(struct ab8500_chargalg *di)
|
||||
*/
|
||||
static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di)
|
||||
{
|
||||
if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg ||
|
||||
di->susp_status.suspended_change) {
|
||||
/*
|
||||
* Charger state changed or suspension
|
||||
* has changed since last update
|
||||
*/
|
||||
if ((di->chg_info.conn_chg & AC_CHG) &&
|
||||
!di->susp_status.ac_suspended) {
|
||||
dev_dbg(di->dev, "Charging source is AC\n");
|
||||
if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg) {
|
||||
/* Charger state changed since last update */
|
||||
if (di->chg_info.conn_chg & AC_CHG) {
|
||||
dev_info(di->dev, "Charging source is AC\n");
|
||||
if (di->chg_info.charger_type != AC_CHG) {
|
||||
di->chg_info.charger_type = AC_CHG;
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
}
|
||||
} else if ((di->chg_info.conn_chg & USB_CHG) &&
|
||||
!di->susp_status.usb_suspended) {
|
||||
dev_dbg(di->dev, "Charging source is USB\n");
|
||||
} else if (di->chg_info.conn_chg & USB_CHG) {
|
||||
dev_info(di->dev, "Charging source is USB\n");
|
||||
di->chg_info.charger_type = USB_CHG;
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
} else if (di->chg_info.conn_chg &&
|
||||
(di->susp_status.ac_suspended ||
|
||||
di->susp_status.usb_suspended)) {
|
||||
dev_dbg(di->dev, "Charging is suspended\n");
|
||||
di->chg_info.charger_type = NO_CHG;
|
||||
ab8500_chargalg_state_to(di, STATE_SUSPENDED_INIT);
|
||||
} else {
|
||||
dev_dbg(di->dev, "Charging source is OFF\n");
|
||||
di->chg_info.charger_type = NO_CHG;
|
||||
ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT);
|
||||
}
|
||||
di->chg_info.prev_conn_chg = di->chg_info.conn_chg;
|
||||
di->susp_status.suspended_change = false;
|
||||
}
|
||||
return di->chg_info.conn_chg;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_chargalg_check_current_step_status() - Check charging current
|
||||
* step status.
|
||||
* @di: pointer to the ab8500_chargalg structure
|
||||
*
|
||||
* This function will check if there is a change in the charging current step
|
||||
* and change charge state accordingly.
|
||||
*/
|
||||
static void ab8500_chargalg_check_current_step_status
|
||||
(struct ab8500_chargalg *di)
|
||||
{
|
||||
if (di->curr_status.curr_step_change)
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
di->curr_status.curr_step_change = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_chargalg_start_safety_timer() - Start charging safety timer
|
||||
* @di: pointer to the ab8500_chargalg structure
|
||||
@ -484,7 +431,7 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
|
||||
/**
|
||||
* ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer
|
||||
* @di: pointer to the ab8500_chargalg structure
|
||||
* @duration: duration of ther maintenance timer in hours
|
||||
* @duration: duration of the maintenance timer in minutes
|
||||
*
|
||||
* The maintenance timer is used to maintain the charge in the battery once
|
||||
* the battery is considered full. These timers are chosen to match the
|
||||
@ -493,9 +440,10 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
|
||||
static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di,
|
||||
int duration)
|
||||
{
|
||||
/* Set a timer in minutes with a 30 second range */
|
||||
hrtimer_set_expires_range(&di->maintenance_timer,
|
||||
ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
|
||||
ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
|
||||
ktime_set(duration * 60, 0),
|
||||
ktime_set(30, 0));
|
||||
di->events.maintenance_timer_expired = false;
|
||||
hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
|
||||
}
|
||||
@ -737,26 +685,31 @@ static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di)
|
||||
di->batt_data.temp < (bi->temp_alert_max - di->t_hyst_norm)) {
|
||||
/* Temp OK! */
|
||||
di->events.btemp_underover = false;
|
||||
di->events.btemp_lowhigh = false;
|
||||
di->events.btemp_low = false;
|
||||
di->events.btemp_high = false;
|
||||
di->t_hyst_norm = 0;
|
||||
di->t_hyst_lowhigh = 0;
|
||||
} else {
|
||||
if (((di->batt_data.temp >= bi->temp_alert_max) &&
|
||||
(di->batt_data.temp <
|
||||
(bi->temp_max - di->t_hyst_lowhigh))) ||
|
||||
((di->batt_data.temp >
|
||||
(bi->temp_min + di->t_hyst_lowhigh)) &&
|
||||
(di->batt_data.temp <= bi->temp_alert_min))) {
|
||||
/* TEMP minor!!!!! */
|
||||
if ((di->batt_data.temp >= bi->temp_alert_max) &&
|
||||
(di->batt_data.temp < (bi->temp_max - di->t_hyst_lowhigh))) {
|
||||
/* Alert zone for high temperature */
|
||||
di->events.btemp_underover = false;
|
||||
di->events.btemp_lowhigh = true;
|
||||
di->events.btemp_high = true;
|
||||
di->t_hyst_norm = di->bm->temp_hysteresis;
|
||||
di->t_hyst_lowhigh = 0;
|
||||
} else if ((di->batt_data.temp > (bi->temp_min + di->t_hyst_lowhigh)) &&
|
||||
(di->batt_data.temp <= bi->temp_alert_min)) {
|
||||
/* Alert zone for low temperature */
|
||||
di->events.btemp_underover = false;
|
||||
di->events.btemp_low = true;
|
||||
di->t_hyst_norm = di->bm->temp_hysteresis;
|
||||
di->t_hyst_lowhigh = 0;
|
||||
} else if (di->batt_data.temp <= bi->temp_min ||
|
||||
di->batt_data.temp >= bi->temp_max) {
|
||||
/* TEMP major!!!!! */
|
||||
di->events.btemp_underover = true;
|
||||
di->events.btemp_lowhigh = false;
|
||||
di->events.btemp_low = false;
|
||||
di->events.btemp_high = false;
|
||||
di->t_hyst_norm = 0;
|
||||
di->t_hyst_lowhigh = di->bm->temp_hysteresis;
|
||||
} else {
|
||||
@ -802,7 +755,7 @@ static void ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di)
|
||||
if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
|
||||
di->charge_state == STATE_NORMAL &&
|
||||
!di->maintenance_chg && (di->batt_data.volt_uv >=
|
||||
di->bm->bi->overvoltage_limit_uv ||
|
||||
di->bm->bi->voltage_max_design_uv ||
|
||||
di->events.usb_cv_active || di->events.ac_cv_active) &&
|
||||
di->batt_data.avg_curr_ua <
|
||||
di->bm->bi->charge_term_current_ua &&
|
||||
@ -831,7 +784,6 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di)
|
||||
|
||||
di->ccm.original_iset_ua = bi->constant_charge_current_max_ua;
|
||||
di->ccm.current_iset_ua = bi->constant_charge_current_max_ua;
|
||||
di->ccm.test_delta_i_ua = di->bm->maxi->charger_curr_step_ua;
|
||||
di->ccm.max_current_ua = di->bm->maxi->chg_curr_ua;
|
||||
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
|
||||
di->ccm.level = 0;
|
||||
@ -848,13 +800,10 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di)
|
||||
*/
|
||||
static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di)
|
||||
{
|
||||
int delta_i_ua;
|
||||
|
||||
if (!di->bm->maxi->ena_maxi)
|
||||
return MAXIM_RET_NOACTION;
|
||||
|
||||
delta_i_ua = di->ccm.original_iset_ua - di->batt_data.inst_curr_ua;
|
||||
|
||||
if (di->events.vbus_collapsed) {
|
||||
dev_dbg(di->dev, "Charger voltage has collapsed %d\n",
|
||||
di->ccm.wait_cnt);
|
||||
@ -862,8 +811,7 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di)
|
||||
dev_dbg(di->dev, "lowering current\n");
|
||||
di->ccm.wait_cnt++;
|
||||
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
|
||||
di->ccm.max_current_ua =
|
||||
di->ccm.current_iset_ua - di->ccm.test_delta_i_ua;
|
||||
di->ccm.max_current_ua = di->ccm.current_iset_ua;
|
||||
di->ccm.current_iset_ua = di->ccm.max_current_ua;
|
||||
di->ccm.level--;
|
||||
return MAXIM_RET_CHANGE;
|
||||
@ -893,29 +841,8 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di)
|
||||
return MAXIM_RET_IBAT_TOO_HIGH;
|
||||
}
|
||||
|
||||
if (delta_i_ua > di->ccm.test_delta_i_ua &&
|
||||
(di->ccm.current_iset_ua + di->ccm.test_delta_i_ua) <
|
||||
di->ccm.max_current_ua) {
|
||||
if (di->ccm.condition_cnt-- == 0) {
|
||||
/* Increse the iset with cco.test_delta_i */
|
||||
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
|
||||
di->ccm.current_iset_ua += di->ccm.test_delta_i_ua;
|
||||
di->ccm.level++;
|
||||
dev_dbg(di->dev, " Maximization needed, increase"
|
||||
" with %d uA to %duA (Optimal ibat: %d uA)"
|
||||
" Level %d\n",
|
||||
di->ccm.test_delta_i_ua,
|
||||
di->ccm.current_iset_ua,
|
||||
di->ccm.original_iset_ua,
|
||||
di->ccm.level);
|
||||
return MAXIM_RET_CHANGE;
|
||||
} else {
|
||||
return MAXIM_RET_NOACTION;
|
||||
}
|
||||
} else {
|
||||
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
|
||||
return MAXIM_RET_NOACTION;
|
||||
}
|
||||
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
|
||||
return MAXIM_RET_NOACTION;
|
||||
}
|
||||
|
||||
static void handle_maxim_chg_curr(struct ab8500_chargalg *di)
|
||||
@ -1300,9 +1227,9 @@ static void ab8500_chargalg_external_power_changed(struct power_supply *psy)
|
||||
static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
{
|
||||
struct power_supply_battery_info *bi = di->bm->bi;
|
||||
struct power_supply_maintenance_charge_table *mt;
|
||||
int charger_status;
|
||||
int ret;
|
||||
int curr_step_lvl_ua;
|
||||
|
||||
/* Collect data from all power_supply class devices */
|
||||
class_for_each_device(power_supply_class, NULL,
|
||||
@ -1313,7 +1240,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
ab8500_chargalg_check_charger_voltage(di);
|
||||
|
||||
charger_status = ab8500_chargalg_check_charger_connection(di);
|
||||
ab8500_chargalg_check_current_step_status(di);
|
||||
|
||||
if (is_ab8500(di->parent)) {
|
||||
ret = ab8500_chargalg_check_charger_enable(di);
|
||||
@ -1335,12 +1261,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
}
|
||||
}
|
||||
|
||||
/* If suspended, we should not continue checking the flags */
|
||||
else if (di->charge_state == STATE_SUSPENDED_INIT ||
|
||||
di->charge_state == STATE_SUSPENDED) {
|
||||
/* We don't do anything here, just don,t continue */
|
||||
}
|
||||
|
||||
/* Safety timer expiration */
|
||||
else if (di->events.safety_timer_expired) {
|
||||
if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED)
|
||||
@ -1348,7 +1268,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
STATE_SAFETY_TIMER_EXPIRED_INIT);
|
||||
}
|
||||
/*
|
||||
* Check if any interrupts has occured
|
||||
* Check if any interrupts has occurred
|
||||
* that will prevent us from charging
|
||||
*/
|
||||
|
||||
@ -1396,7 +1316,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT);
|
||||
}
|
||||
/* Battery temp high/low */
|
||||
else if (di->events.btemp_lowhigh) {
|
||||
else if (di->events.btemp_low || di->events.btemp_high) {
|
||||
if (di->charge_state != STATE_TEMP_LOWHIGH)
|
||||
ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT);
|
||||
}
|
||||
@ -1438,23 +1358,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
case STATE_HANDHELD:
|
||||
break;
|
||||
|
||||
case STATE_SUSPENDED_INIT:
|
||||
if (di->susp_status.ac_suspended)
|
||||
ab8500_chargalg_ac_en(di, false, 0, 0);
|
||||
if (di->susp_status.usb_suspended)
|
||||
ab8500_chargalg_usb_en(di, false, 0, 0);
|
||||
ab8500_chargalg_stop_safety_timer(di);
|
||||
ab8500_chargalg_stop_maintenance_timer(di);
|
||||
di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
di->maintenance_chg = false;
|
||||
ab8500_chargalg_state_to(di, STATE_SUSPENDED);
|
||||
power_supply_changed(di->chargalg_psy);
|
||||
fallthrough;
|
||||
|
||||
case STATE_SUSPENDED:
|
||||
/* CHARGING is suspended */
|
||||
break;
|
||||
|
||||
case STATE_BATT_REMOVED_INIT:
|
||||
ab8500_chargalg_stop_charging(di);
|
||||
ab8500_chargalg_state_to(di, STATE_BATT_REMOVED);
|
||||
@ -1511,15 +1414,13 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
break;
|
||||
|
||||
case STATE_NORMAL_INIT:
|
||||
if (di->curr_status.curr_step_ua == CHARGALG_CURR_STEP_LOW_UA)
|
||||
if (bi->constant_charge_current_max_ua == 0)
|
||||
/* "charging" with 0 uA */
|
||||
ab8500_chargalg_stop_charging(di);
|
||||
else {
|
||||
curr_step_lvl_ua = bi->constant_charge_current_max_ua
|
||||
* di->curr_status.curr_step_ua
|
||||
/ CHARGALG_CURR_STEP_HIGH_UA;
|
||||
ab8500_chargalg_start_charging(di,
|
||||
bi->constant_charge_voltage_max_uv,
|
||||
curr_step_lvl_ua);
|
||||
bi->constant_charge_current_max_ua);
|
||||
}
|
||||
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL);
|
||||
@ -1537,7 +1438,12 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
handle_maxim_chg_curr(di);
|
||||
if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
|
||||
di->maintenance_chg) {
|
||||
if (di->bm->no_maintenance)
|
||||
/*
|
||||
* The battery is fully charged, check if we support
|
||||
* maintenance charging else go back to waiting for
|
||||
* the recharge voltage limit.
|
||||
*/
|
||||
if (!power_supply_supports_maintenance_charging(bi))
|
||||
ab8500_chargalg_state_to(di,
|
||||
STATE_WAIT_FOR_RECHARGE_INIT);
|
||||
else
|
||||
@ -1558,12 +1464,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
break;
|
||||
|
||||
case STATE_MAINTENANCE_A_INIT:
|
||||
mt = power_supply_get_maintenance_charging_setting(bi, 0);
|
||||
if (!mt) {
|
||||
/* No maintenance A state, go back to normal */
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
power_supply_changed(di->chargalg_psy);
|
||||
break;
|
||||
}
|
||||
ab8500_chargalg_stop_safety_timer(di);
|
||||
ab8500_chargalg_start_maintenance_timer(di,
|
||||
di->bm->bat_type->maint_a_chg_timer_h);
|
||||
mt->charge_safety_timer_minutes);
|
||||
ab8500_chargalg_start_charging(di,
|
||||
di->bm->bat_type->maint_a_vol_lvl,
|
||||
di->bm->bat_type->maint_a_cur_lvl);
|
||||
mt->charge_voltage_max_uv,
|
||||
mt->charge_current_max_ua);
|
||||
ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A);
|
||||
power_supply_changed(di->chargalg_psy);
|
||||
fallthrough;
|
||||
@ -1576,11 +1489,18 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
break;
|
||||
|
||||
case STATE_MAINTENANCE_B_INIT:
|
||||
mt = power_supply_get_maintenance_charging_setting(bi, 1);
|
||||
if (!mt) {
|
||||
/* No maintenance B state, go back to normal */
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
power_supply_changed(di->chargalg_psy);
|
||||
break;
|
||||
}
|
||||
ab8500_chargalg_start_maintenance_timer(di,
|
||||
di->bm->bat_type->maint_b_chg_timer_h);
|
||||
mt->charge_safety_timer_minutes);
|
||||
ab8500_chargalg_start_charging(di,
|
||||
di->bm->bat_type->maint_b_vol_lvl,
|
||||
di->bm->bat_type->maint_b_cur_lvl);
|
||||
mt->charge_voltage_max_uv,
|
||||
mt->charge_current_max_ua);
|
||||
ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B);
|
||||
power_supply_changed(di->chargalg_psy);
|
||||
fallthrough;
|
||||
@ -1593,9 +1513,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
break;
|
||||
|
||||
case STATE_TEMP_LOWHIGH_INIT:
|
||||
ab8500_chargalg_start_charging(di,
|
||||
di->bm->bat_type->low_high_vol_lvl,
|
||||
di->bm->bat_type->low_high_cur_lvl);
|
||||
if (di->events.btemp_low) {
|
||||
ab8500_chargalg_start_charging(di,
|
||||
bi->alert_low_temp_charge_voltage_uv,
|
||||
bi->alert_low_temp_charge_current_ua);
|
||||
} else if (di->events.btemp_high) {
|
||||
ab8500_chargalg_start_charging(di,
|
||||
bi->alert_high_temp_charge_voltage_uv,
|
||||
bi->alert_high_temp_charge_current_ua);
|
||||
} else {
|
||||
dev_err(di->dev, "neither low or high temp event occurred\n");
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
break;
|
||||
}
|
||||
ab8500_chargalg_stop_maintenance_timer(di);
|
||||
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
|
||||
@ -1603,7 +1533,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
|
||||
fallthrough;
|
||||
|
||||
case STATE_TEMP_LOWHIGH:
|
||||
if (!di->events.btemp_lowhigh)
|
||||
if (!di->events.btemp_low && !di->events.btemp_high)
|
||||
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
|
||||
break;
|
||||
|
||||
@ -1740,180 +1670,6 @@ static int ab8500_chargalg_get_property(struct power_supply *psy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Exposure to the sysfs interface */
|
||||
|
||||
static ssize_t ab8500_chargalg_curr_step_show(struct ab8500_chargalg *di,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", di->curr_status.curr_step_ua);
|
||||
}
|
||||
|
||||
static ssize_t ab8500_chargalg_curr_step_store(struct ab8500_chargalg *di,
|
||||
const char *buf, size_t length)
|
||||
{
|
||||
long param;
|
||||
int ret;
|
||||
|
||||
ret = kstrtol(buf, 10, ¶m);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
di->curr_status.curr_step_ua = param;
|
||||
if (di->curr_status.curr_step_ua >= CHARGALG_CURR_STEP_LOW_UA &&
|
||||
di->curr_status.curr_step_ua <= CHARGALG_CURR_STEP_HIGH_UA) {
|
||||
di->curr_status.curr_step_change = true;
|
||||
queue_work(di->chargalg_wq, &di->chargalg_work);
|
||||
} else
|
||||
dev_info(di->dev, "Wrong current step\n"
|
||||
"Enter 0. Disable AC/USB Charging\n"
|
||||
"1--100. Set AC/USB charging current step\n"
|
||||
"100. Enable AC/USB Charging\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t ab8500_chargalg_en_show(struct ab8500_chargalg *di,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n",
|
||||
di->susp_status.ac_suspended &&
|
||||
di->susp_status.usb_suspended);
|
||||
}
|
||||
|
||||
static ssize_t ab8500_chargalg_en_store(struct ab8500_chargalg *di,
|
||||
const char *buf, size_t length)
|
||||
{
|
||||
long param;
|
||||
int ac_usb;
|
||||
int ret;
|
||||
|
||||
ret = kstrtol(buf, 10, ¶m);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ac_usb = param;
|
||||
switch (ac_usb) {
|
||||
case 0:
|
||||
/* Disable charging */
|
||||
di->susp_status.ac_suspended = true;
|
||||
di->susp_status.usb_suspended = true;
|
||||
di->susp_status.suspended_change = true;
|
||||
/* Trigger a state change */
|
||||
queue_work(di->chargalg_wq,
|
||||
&di->chargalg_work);
|
||||
break;
|
||||
case 1:
|
||||
/* Enable AC Charging */
|
||||
di->susp_status.ac_suspended = false;
|
||||
di->susp_status.suspended_change = true;
|
||||
/* Trigger a state change */
|
||||
queue_work(di->chargalg_wq,
|
||||
&di->chargalg_work);
|
||||
break;
|
||||
case 2:
|
||||
/* Enable USB charging */
|
||||
di->susp_status.usb_suspended = false;
|
||||
di->susp_status.suspended_change = true;
|
||||
/* Trigger a state change */
|
||||
queue_work(di->chargalg_wq,
|
||||
&di->chargalg_work);
|
||||
break;
|
||||
default:
|
||||
dev_info(di->dev, "Wrong input\n"
|
||||
"Enter 0. Disable AC/USB Charging\n"
|
||||
"1. Enable AC charging\n"
|
||||
"2. Enable USB Charging\n");
|
||||
}
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static struct ab8500_chargalg_sysfs_entry ab8500_chargalg_en_charger =
|
||||
__ATTR(chargalg, 0644, ab8500_chargalg_en_show,
|
||||
ab8500_chargalg_en_store);
|
||||
|
||||
static struct ab8500_chargalg_sysfs_entry ab8500_chargalg_curr_step =
|
||||
__ATTR(chargalg_curr_step, 0644, ab8500_chargalg_curr_step_show,
|
||||
ab8500_chargalg_curr_step_store);
|
||||
|
||||
static ssize_t ab8500_chargalg_sysfs_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
{
|
||||
struct ab8500_chargalg_sysfs_entry *entry = container_of(attr,
|
||||
struct ab8500_chargalg_sysfs_entry, attr);
|
||||
|
||||
struct ab8500_chargalg *di = container_of(kobj,
|
||||
struct ab8500_chargalg, chargalg_kobject);
|
||||
|
||||
if (!entry->show)
|
||||
return -EIO;
|
||||
|
||||
return entry->show(di, buf);
|
||||
}
|
||||
|
||||
static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj,
|
||||
struct attribute *attr, const char *buf, size_t length)
|
||||
{
|
||||
struct ab8500_chargalg_sysfs_entry *entry = container_of(attr,
|
||||
struct ab8500_chargalg_sysfs_entry, attr);
|
||||
|
||||
struct ab8500_chargalg *di = container_of(kobj,
|
||||
struct ab8500_chargalg, chargalg_kobject);
|
||||
|
||||
if (!entry->store)
|
||||
return -EIO;
|
||||
|
||||
return entry->store(di, buf, length);
|
||||
}
|
||||
|
||||
static struct attribute *ab8500_chargalg_chg[] = {
|
||||
&ab8500_chargalg_en_charger.attr,
|
||||
&ab8500_chargalg_curr_step.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct sysfs_ops ab8500_chargalg_sysfs_ops = {
|
||||
.show = ab8500_chargalg_sysfs_show,
|
||||
.store = ab8500_chargalg_sysfs_charger,
|
||||
};
|
||||
|
||||
static struct kobj_type ab8500_chargalg_ktype = {
|
||||
.sysfs_ops = &ab8500_chargalg_sysfs_ops,
|
||||
.default_attrs = ab8500_chargalg_chg,
|
||||
};
|
||||
|
||||
/**
|
||||
* ab8500_chargalg_sysfs_exit() - de-init of sysfs entry
|
||||
* @di: pointer to the struct ab8500_chargalg
|
||||
*
|
||||
* This function removes the entry in sysfs.
|
||||
*/
|
||||
static void ab8500_chargalg_sysfs_exit(struct ab8500_chargalg *di)
|
||||
{
|
||||
kobject_del(&di->chargalg_kobject);
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_chargalg_sysfs_init() - init of sysfs entry
|
||||
* @di: pointer to the struct ab8500_chargalg
|
||||
*
|
||||
* This function adds an entry in sysfs.
|
||||
* Returns error code in case of failure else 0(on success)
|
||||
*/
|
||||
static int ab8500_chargalg_sysfs_init(struct ab8500_chargalg *di)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = kobject_init_and_add(&di->chargalg_kobject,
|
||||
&ab8500_chargalg_ktype,
|
||||
NULL, "ab8500_chargalg");
|
||||
if (ret < 0)
|
||||
dev_err(di->dev, "failed to create sysfs entry\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
/* Exposure to the sysfs interface <<END>> */
|
||||
|
||||
static int __maybe_unused ab8500_chargalg_resume(struct device *dev)
|
||||
{
|
||||
struct ab8500_chargalg *di = dev_get_drvdata(dev);
|
||||
@ -2003,7 +1759,6 @@ static int ab8500_chargalg_probe(struct platform_device *pdev)
|
||||
struct device *dev = &pdev->dev;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct ab8500_chargalg *di;
|
||||
int ret = 0;
|
||||
|
||||
di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
|
||||
if (!di)
|
||||
@ -2020,11 +1775,11 @@ static int ab8500_chargalg_probe(struct platform_device *pdev)
|
||||
psy_cfg.drv_data = di;
|
||||
|
||||
/* Initilialize safety timer */
|
||||
hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
|
||||
hrtimer_init(&di->safety_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
di->safety_timer.function = ab8500_chargalg_safety_timer_expired;
|
||||
|
||||
/* Initilialize maintenance timer */
|
||||
hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
|
||||
hrtimer_init(&di->maintenance_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
di->maintenance_timer.function =
|
||||
ab8500_chargalg_maintenance_timer_expired;
|
||||
|
||||
@ -2051,27 +1806,14 @@ static int ab8500_chargalg_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, di);
|
||||
|
||||
/* sysfs interface to enable/disable charging from user space */
|
||||
ret = ab8500_chargalg_sysfs_init(di);
|
||||
if (ret) {
|
||||
dev_err(di->dev, "failed to create sysfs entry\n");
|
||||
return ret;
|
||||
}
|
||||
di->curr_status.curr_step_ua = CHARGALG_CURR_STEP_HIGH_UA;
|
||||
|
||||
dev_info(di->dev, "probe success\n");
|
||||
return component_add(dev, &ab8500_chargalg_component_ops);
|
||||
}
|
||||
|
||||
static int ab8500_chargalg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ab8500_chargalg *di = platform_get_drvdata(pdev);
|
||||
|
||||
component_del(&pdev->dev, &ab8500_chargalg_component_ops);
|
||||
|
||||
/* sysfs interface to enable/disable charging from user space */
|
||||
ab8500_chargalg_sysfs_exit(di);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ enum ab8500_usb_state {
|
||||
#define USB_CH_IP_CUR_LVL_1P4 1400000
|
||||
#define USB_CH_IP_CUR_LVL_1P5 1500000
|
||||
|
||||
#define VBAT_TRESH_IP_CUR_RED 3800
|
||||
#define VBAT_TRESH_IP_CUR_RED 3800000
|
||||
|
||||
#define to_ab8500_charger_usb_device_info(x) container_of((x), \
|
||||
struct ab8500_charger, usb_chg)
|
||||
@ -171,7 +171,7 @@ enum ab8500_usb_state {
|
||||
struct ab8500_charger, ac_chg)
|
||||
|
||||
/**
|
||||
* struct ab8500_charger_interrupts - ab8500 interupts
|
||||
* struct ab8500_charger_interrupts - ab8500 interrupts
|
||||
* @name: name of the interrupt
|
||||
* @isr function pointer to the isr
|
||||
*/
|
||||
@ -1083,7 +1083,7 @@ static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr_ua)
|
||||
|
||||
/**
|
||||
* ab8500_charger_get_usb_cur() - get usb current
|
||||
* @di: pointer to the ab8500_charger structre
|
||||
* @di: pointer to the ab8500_charger structure
|
||||
*
|
||||
* The usb stack provides the maximum current that can be drawn from
|
||||
* the standard usb host. This will be in uA.
|
||||
@ -1920,7 +1920,11 @@ static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
|
||||
|
||||
di = to_ab8500_charger_usb_device_info(usb_chg);
|
||||
|
||||
/* For all psy where the driver name appears in any supplied_to */
|
||||
/*
|
||||
* For all psy where the driver name appears in any supplied_to
|
||||
* in practice what we will find will always be "ab8500_fg" as
|
||||
* the fuel gauge is responsible of keeping track of VBAT.
|
||||
*/
|
||||
j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
|
||||
if (j < 0)
|
||||
return 0;
|
||||
@ -1937,7 +1941,10 @@ static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
switch (ext->desc->type) {
|
||||
case POWER_SUPPLY_TYPE_BATTERY:
|
||||
di->vbat = ret.intval / 1000;
|
||||
/* This will always be "ab8500_fg" */
|
||||
dev_dbg(di->dev, "get VBAT from %s\n",
|
||||
dev_name(&ext->dev));
|
||||
di->vbat = ret.intval;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1966,7 +1973,7 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work)
|
||||
struct ab8500_charger, check_vbat_work.work);
|
||||
|
||||
class_for_each_device(power_supply_class, NULL,
|
||||
di->usb_chg.psy, ab8500_charger_get_ext_psy_data);
|
||||
&di->usb_chg, ab8500_charger_get_ext_psy_data);
|
||||
|
||||
/* First run old_vbat is 0. */
|
||||
if (di->old_vbat == 0)
|
||||
@ -1991,8 +1998,8 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work)
|
||||
* No need to check the battery voltage every second when not close to
|
||||
* the threshold.
|
||||
*/
|
||||
if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) &&
|
||||
(di->vbat > (VBAT_TRESH_IP_CUR_RED - 100)))
|
||||
if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100000) &&
|
||||
(di->vbat > (VBAT_TRESH_IP_CUR_RED - 100000)))
|
||||
t = 1;
|
||||
|
||||
queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ);
|
||||
@ -3443,17 +3450,19 @@ static int ab8500_charger_probe(struct platform_device *pdev)
|
||||
di->parent = dev_get_drvdata(pdev->dev.parent);
|
||||
|
||||
/* Get ADC channels */
|
||||
di->adc_main_charger_v = devm_iio_channel_get(dev, "main_charger_v");
|
||||
if (IS_ERR(di->adc_main_charger_v)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_v),
|
||||
"failed to get ADC main charger voltage\n");
|
||||
return ret;
|
||||
}
|
||||
di->adc_main_charger_c = devm_iio_channel_get(dev, "main_charger_c");
|
||||
if (IS_ERR(di->adc_main_charger_c)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_c),
|
||||
"failed to get ADC main charger current\n");
|
||||
return ret;
|
||||
if (!is_ab8505(di->parent)) {
|
||||
di->adc_main_charger_v = devm_iio_channel_get(dev, "main_charger_v");
|
||||
if (IS_ERR(di->adc_main_charger_v)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_v),
|
||||
"failed to get ADC main charger voltage\n");
|
||||
return ret;
|
||||
}
|
||||
di->adc_main_charger_c = devm_iio_channel_get(dev, "main_charger_c");
|
||||
if (IS_ERR(di->adc_main_charger_c)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_c),
|
||||
"failed to get ADC main charger current\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
di->adc_vbus_v = devm_iio_channel_get(dev, "vbus_v");
|
||||
if (IS_ERR(di->adc_vbus_v)) {
|
||||
|
@ -45,6 +45,9 @@
|
||||
#define SEC_TO_SAMPLE(S) (S * 4)
|
||||
|
||||
#define NBR_AVG_SAMPLES 20
|
||||
#define WAIT_FOR_INST_CURRENT_MAX 70
|
||||
/* Currents higher than -500mA (dissipating) will make compensation unstable */
|
||||
#define IGNORE_VBAT_HIGHCUR -500000
|
||||
|
||||
#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */
|
||||
|
||||
@ -210,6 +213,7 @@ struct ab8500_fg {
|
||||
int init_cnt;
|
||||
int low_bat_cnt;
|
||||
int nbr_cceoc_irq_cnt;
|
||||
u32 line_impedance_uohm;
|
||||
bool recovery_needed;
|
||||
bool high_curr_mode;
|
||||
bool init_capacity;
|
||||
@ -874,27 +878,41 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
|
||||
/**
|
||||
* ab8500_fg_battery_resistance() - Returns the battery inner resistance
|
||||
* @di: pointer to the ab8500_fg structure
|
||||
* @vbat_uncomp_uv: Uncompensated VBAT voltage
|
||||
*
|
||||
* Returns battery inner resistance added with the fuel gauge resistor value
|
||||
* to get the total resistance in the whole link from gnd to bat+ node
|
||||
* in milliohm.
|
||||
*/
|
||||
static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
|
||||
static int ab8500_fg_battery_resistance(struct ab8500_fg *di, int vbat_uncomp_uv)
|
||||
{
|
||||
struct power_supply_battery_info *bi = di->bm->bi;
|
||||
int resistance_percent = 0;
|
||||
int resistance;
|
||||
|
||||
resistance_percent = power_supply_temp2resist_simple(bi->resist_table,
|
||||
bi->resist_table_size,
|
||||
di->bat_temp / 10);
|
||||
/*
|
||||
* We get a percentage of factory resistance here so first get
|
||||
* the factory resistance in milliohms then calculate how much
|
||||
* resistance we have at this temperature.
|
||||
* Determine the resistance at this voltage. First try VBAT-to-Ri else
|
||||
* just infer it from the surrounding temperature, if nothing works just
|
||||
* use the internal resistance.
|
||||
*/
|
||||
resistance = (bi->factory_internal_resistance_uohm / 1000);
|
||||
resistance = resistance * resistance_percent / 100;
|
||||
if (power_supply_supports_vbat2ri(bi)) {
|
||||
resistance = power_supply_vbat2ri(bi, vbat_uncomp_uv, di->flags.charging);
|
||||
/* Convert to milliohm */
|
||||
resistance = resistance / 1000;
|
||||
} else if (power_supply_supports_temp2ri(bi)) {
|
||||
resistance_percent = power_supply_temp2resist_simple(bi->resist_table,
|
||||
bi->resist_table_size,
|
||||
di->bat_temp / 10);
|
||||
/* Convert to milliohm */
|
||||
resistance = bi->factory_internal_resistance_uohm / 1000;
|
||||
resistance = resistance * resistance_percent / 100;
|
||||
} else {
|
||||
/* Last fallback */
|
||||
resistance = bi->factory_internal_resistance_uohm / 1000;
|
||||
}
|
||||
|
||||
/* Compensate for line impedance */
|
||||
resistance += (di->line_impedance_uohm / 1000);
|
||||
|
||||
dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
|
||||
" fg resistance %d, total: %d (mOhm)\n",
|
||||
@ -907,6 +925,60 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
|
||||
return resistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_load_comp_fg_bat_voltage() - get load compensated battery voltage
|
||||
* @di: pointer to the ab8500_fg structure
|
||||
* @always: always return a voltage, also uncompensated
|
||||
*
|
||||
* Returns compensated battery voltage (on success) else error code.
|
||||
* If always is specified, we always return a voltage but it may be
|
||||
* uncompensated.
|
||||
*/
|
||||
static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
|
||||
{
|
||||
int i = 0;
|
||||
int vbat_uv = 0;
|
||||
int rcomp;
|
||||
|
||||
/* Average the instant current to get a stable current measurement */
|
||||
ab8500_fg_inst_curr_start(di);
|
||||
|
||||
do {
|
||||
vbat_uv += ab8500_fg_bat_voltage(di);
|
||||
i++;
|
||||
usleep_range(5000, 6000);
|
||||
} while (!ab8500_fg_inst_curr_done(di) &&
|
||||
i <= WAIT_FOR_INST_CURRENT_MAX);
|
||||
|
||||
if (i > WAIT_FOR_INST_CURRENT_MAX) {
|
||||
dev_err(di->dev,
|
||||
"TIMEOUT: return uncompensated measurement of VBAT\n");
|
||||
di->vbat_uv = vbat_uv / i;
|
||||
return di->vbat_uv;
|
||||
}
|
||||
|
||||
ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
|
||||
|
||||
/*
|
||||
* If there is too high current dissipation, the compensation cannot be
|
||||
* trusted so return an error unless we must return something here, as
|
||||
* enforced by the "always" parameter.
|
||||
*/
|
||||
if (!always && di->inst_curr_ua < IGNORE_VBAT_HIGHCUR)
|
||||
return -EINVAL;
|
||||
|
||||
vbat_uv = vbat_uv / i;
|
||||
|
||||
/* Next we apply voltage compensation from internal resistance */
|
||||
rcomp = ab8500_fg_battery_resistance(di, vbat_uv);
|
||||
vbat_uv = vbat_uv - (di->inst_curr_ua * rcomp) / 1000;
|
||||
|
||||
/* Always keep this state at latest measurement */
|
||||
di->vbat_uv = vbat_uv;
|
||||
|
||||
return vbat_uv;
|
||||
}
|
||||
|
||||
/**
|
||||
* ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity
|
||||
* @di: pointer to the ab8500_fg structure
|
||||
@ -916,32 +988,9 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
|
||||
*/
|
||||
static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di)
|
||||
{
|
||||
int vbat_comp_uv, res;
|
||||
int i = 0;
|
||||
int vbat_uv = 0;
|
||||
int vbat_comp_uv;
|
||||
|
||||
ab8500_fg_inst_curr_start(di);
|
||||
|
||||
do {
|
||||
vbat_uv += ab8500_fg_bat_voltage(di);
|
||||
i++;
|
||||
usleep_range(5000, 6000);
|
||||
} while (!ab8500_fg_inst_curr_done(di));
|
||||
|
||||
ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
|
||||
|
||||
di->vbat_uv = vbat_uv / i;
|
||||
res = ab8500_fg_battery_resistance(di);
|
||||
|
||||
/*
|
||||
* Use Ohms law to get the load compensated voltage.
|
||||
* Divide by 1000 to get from milliohms to ohms.
|
||||
*/
|
||||
vbat_comp_uv = di->vbat_uv - (di->inst_curr_ua * res) / 1000;
|
||||
|
||||
dev_dbg(di->dev, "%s Measured Vbat: %d uV,Compensated Vbat %d uV, "
|
||||
"R: %d mOhm, Current: %d uA Vbat Samples: %d\n",
|
||||
__func__, di->vbat_uv, vbat_comp_uv, res, di->inst_curr_ua, i);
|
||||
vbat_comp_uv = ab8500_load_comp_fg_bat_voltage(di, true);
|
||||
|
||||
return ab8500_fg_volt_to_capacity(di, vbat_comp_uv);
|
||||
}
|
||||
@ -1039,20 +1088,16 @@ static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di)
|
||||
/**
|
||||
* ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage
|
||||
* @di: pointer to the ab8500_fg structure
|
||||
* @comp: if voltage should be load compensated before capacity calc
|
||||
*
|
||||
* Return the capacity in mAh based on the battery voltage. The voltage can
|
||||
* either be load compensated or not. This value is added to the filter and a
|
||||
* new mean value is calculated and returned.
|
||||
* Return the capacity in mAh based on the load compensated battery voltage.
|
||||
* This value is added to the filter and a new mean value is calculated and
|
||||
* returned.
|
||||
*/
|
||||
static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp)
|
||||
static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di)
|
||||
{
|
||||
int permille, mah;
|
||||
|
||||
if (comp)
|
||||
permille = ab8500_fg_load_comp_volt_to_capacity(di);
|
||||
else
|
||||
permille = ab8500_fg_uncomp_volt_to_capacity(di);
|
||||
permille = ab8500_fg_load_comp_volt_to_capacity(di);
|
||||
|
||||
mah = ab8500_fg_convert_permille_to_mah(di, permille);
|
||||
|
||||
@ -1529,7 +1574,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
|
||||
|
||||
/* Discard the first [x] seconds */
|
||||
if (di->init_cnt > di->bm->fg_params->init_discard_time) {
|
||||
ab8500_fg_calc_cap_discharge_voltage(di, true);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di);
|
||||
|
||||
ab8500_fg_check_capacity_limits(di, true);
|
||||
}
|
||||
@ -1612,7 +1657,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
|
||||
break;
|
||||
}
|
||||
|
||||
ab8500_fg_calc_cap_discharge_voltage(di, true);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di);
|
||||
} else {
|
||||
mutex_lock(&di->cc_lock);
|
||||
if (!di->flags.conv_done) {
|
||||
@ -1646,7 +1691,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
|
||||
break;
|
||||
|
||||
case AB8500_FG_DISCHARGE_WAKEUP:
|
||||
ab8500_fg_calc_cap_discharge_voltage(di, true);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di);
|
||||
|
||||
di->fg_samples = SEC_TO_SAMPLE(
|
||||
di->bm->fg_params->accu_high_curr);
|
||||
@ -1765,7 +1810,7 @@ static void ab8500_fg_periodic_work(struct work_struct *work)
|
||||
|
||||
if (di->init_capacity) {
|
||||
/* Get an initial capacity calculation */
|
||||
ab8500_fg_calc_cap_discharge_voltage(di, true);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di);
|
||||
ab8500_fg_check_capacity_limits(di, true);
|
||||
di->init_capacity = false;
|
||||
|
||||
@ -2211,10 +2256,6 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
|
||||
if (!di->flags.batt_id_received &&
|
||||
(bi && (bi->technology !=
|
||||
POWER_SUPPLY_TECHNOLOGY_UNKNOWN))) {
|
||||
const struct ab8500_battery_type *b;
|
||||
|
||||
b = di->bm->bat_type;
|
||||
|
||||
di->flags.batt_id_received = true;
|
||||
|
||||
di->bat_cap.max_mah_design =
|
||||
@ -2263,7 +2304,13 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Set VBAT OVV threshold */
|
||||
/*
|
||||
* Set VBAT OVV (overvoltage) threshold to 4.75V (typ) this is what
|
||||
* the hardware supports, nothing else can be configured in hardware.
|
||||
* See this as an "outer limit" where the charger will certainly
|
||||
* shut down. Other (lower) overvoltage levels need to be implemented
|
||||
* in software.
|
||||
*/
|
||||
ret = abx500_mask_and_set_register_interruptible(di->dev,
|
||||
AB8500_CHARGER,
|
||||
AB8500_BATT_OVV,
|
||||
@ -2382,7 +2429,7 @@ static void ab8500_fg_reinit_work(struct work_struct *work)
|
||||
if (!di->flags.calibrate) {
|
||||
dev_dbg(di->dev, "Resetting FG state machine to init.\n");
|
||||
ab8500_fg_clear_cap_samples(di);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di, true);
|
||||
ab8500_fg_calc_cap_discharge_voltage(di);
|
||||
ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
|
||||
ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
|
||||
queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
|
||||
@ -2521,8 +2568,10 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
|
||||
ret = kobject_init_and_add(&di->fg_kobject,
|
||||
&ab8500_fg_ktype,
|
||||
NULL, "battery");
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
kobject_put(&di->fg_kobject);
|
||||
dev_err(di->dev, "failed to create sysfs entry\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -3053,6 +3102,11 @@ static int ab8500_fg_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(dev->of_node, "line-impedance-micro-ohms",
|
||||
&di->line_impedance_uohm))
|
||||
dev_info(dev, "line impedance: %u uOhm\n",
|
||||
di->line_impedance_uohm);
|
||||
|
||||
psy_cfg.supplied_to = supply_interface;
|
||||
psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
|
||||
psy_cfg.drv_data = di;
|
||||
@ -3170,7 +3224,6 @@ static int ab8500_fg_probe(struct platform_device *pdev)
|
||||
|
||||
static int ab8500_fg_remove(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct ab8500_fg *di = platform_get_drvdata(pdev);
|
||||
|
||||
component_del(&pdev->dev, &ab8500_fg_component_ops);
|
||||
@ -3178,7 +3231,7 @@ static int ab8500_fg_remove(struct platform_device *pdev)
|
||||
ab8500_fg_sysfs_exit(di);
|
||||
ab8500_fg_sysfs_psy_remove_attrs(di);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(ab8500_fg_pm_ops, ab8500_fg_suspend, ab8500_fg_resume);
|
||||
|
@ -377,11 +377,9 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
|
||||
/* Request irqs after registering, as irqs may trigger immediately */
|
||||
for (i = 0; i < axp_data->num_irq_names; i++) {
|
||||
irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ for %s: %d\n",
|
||||
axp_data->irq_names[i], irq);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
}
|
||||
|
||||
power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
|
||||
ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
|
||||
axp20x_ac_power_irq, 0,
|
||||
|
@ -186,7 +186,6 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
|
||||
struct iio_channel *chan;
|
||||
int ret = 0, reg, val1;
|
||||
|
||||
switch (psp) {
|
||||
@ -266,12 +265,12 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg & AXP20X_PWR_STATUS_BAT_CHARGING)
|
||||
chan = axp20x_batt->batt_chrg_i;
|
||||
else
|
||||
chan = axp20x_batt->batt_dischrg_i;
|
||||
|
||||
ret = iio_read_channel_processed(chan, &val->intval);
|
||||
if (reg & AXP20X_PWR_STATUS_BAT_CHARGING) {
|
||||
ret = iio_read_channel_processed(axp20x_batt->batt_chrg_i, &val->intval);
|
||||
} else {
|
||||
ret = iio_read_channel_processed(axp20x_batt->batt_dischrg_i, &val1);
|
||||
val->intval = -val1;
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -637,11 +637,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
||||
/* Request irqs after registering, as irqs may trigger immediately */
|
||||
for (i = 0; i < axp_data->num_irq_names; i++) {
|
||||
irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ for %s: %d\n",
|
||||
axp_data->irq_names[i], irq);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
}
|
||||
|
||||
power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
|
||||
ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
|
||||
axp20x_usb_power_irq, 0,
|
||||
|
@ -42,11 +42,11 @@
|
||||
#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */
|
||||
#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */
|
||||
#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31
|
||||
#define VBUS_ISPOUT_VHOLD_SET_MASK 0x38
|
||||
#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3
|
||||
#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_4400MV 0x4 /* 4400mV */
|
||||
#define VBUS_ISPOUT_VBUS_PATH_DIS BIT(7)
|
||||
|
||||
#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */
|
||||
@ -769,6 +769,16 @@ static int charger_init_hw_regs(struct axp288_chrg_info *info)
|
||||
ret = axp288_charger_vbus_path_select(info, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
/* Set Vhold to the factory default / recommended 4.4V */
|
||||
val = VBUS_ISPOUT_VHOLD_SET_4400MV << VBUS_ISPOUT_VHOLD_SET_BIT_POS;
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
|
||||
VBUS_ISPOUT_VHOLD_SET_MASK, val);
|
||||
if (ret < 0) {
|
||||
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_VBUS_IPSOUT_MGMT, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read current charge voltage and current limit */
|
||||
@ -828,6 +838,13 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
||||
struct power_supply_config charger_cfg = {};
|
||||
unsigned int val;
|
||||
|
||||
/*
|
||||
* Normally the native AXP288 fg/charger drivers are preferred but
|
||||
* on some devices the ACPI drivers should be used instead.
|
||||
*/
|
||||
if (!acpi_quirk_skip_acpi_ac_and_battery())
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* On some devices the fuelgauge and charger parts of the axp288 are
|
||||
* not used, check that the fuelgauge is enabled (CC_CTRL != 0).
|
||||
|
@ -9,6 +9,7 @@
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -88,6 +89,11 @@
|
||||
|
||||
#define AXP288_REG_UPDATE_INTERVAL (60 * HZ)
|
||||
#define AXP288_FG_INTR_NUM 6
|
||||
|
||||
static bool no_current_sense_res;
|
||||
module_param(no_current_sense_res, bool, 0444);
|
||||
MODULE_PARM_DESC(no_current_sense_res, "No (or broken) current sense resistor");
|
||||
|
||||
enum {
|
||||
QWBTU_IRQ = 0,
|
||||
WBTU_IRQ,
|
||||
@ -107,7 +113,6 @@ enum {
|
||||
struct axp288_fg_info {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct regmap_irq_chip_data *regmap_irqc;
|
||||
int irq[AXP288_FG_INTR_NUM];
|
||||
struct iio_channel *iio_channel[IIO_CHANNEL_NUM];
|
||||
struct power_supply *bat;
|
||||
@ -138,12 +143,13 @@ static enum power_supply_property fuel_gauge_props[] = {
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_OCV,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
|
||||
POWER_SUPPLY_PROP_TECHNOLOGY,
|
||||
/* The 3 props below are not used when no_current_sense_res is set */
|
||||
POWER_SUPPLY_PROP_CHARGE_FULL,
|
||||
POWER_SUPPLY_PROP_CHARGE_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
};
|
||||
|
||||
static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
|
||||
@ -225,7 +231,10 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info)
|
||||
goto out;
|
||||
info->pwr_stat = ret;
|
||||
|
||||
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
|
||||
if (no_current_sense_res)
|
||||
ret = fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG);
|
||||
else
|
||||
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
info->fg_res = ret;
|
||||
@ -234,6 +243,14 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info)
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
info->ocv = ret;
|
||||
|
||||
if (no_current_sense_res)
|
||||
goto out_no_current_sense_res;
|
||||
|
||||
if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) {
|
||||
info->d_curr = 0;
|
||||
ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr);
|
||||
@ -246,11 +263,6 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
info->ocv = ret;
|
||||
|
||||
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
@ -261,6 +273,7 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info)
|
||||
goto out;
|
||||
info->fg_des_cap1 = ret;
|
||||
|
||||
out_no_current_sense_res:
|
||||
info->last_updated = jiffies;
|
||||
info->valid = 1;
|
||||
ret = 0;
|
||||
@ -293,7 +306,7 @@ static void fuel_gauge_get_status(struct axp288_fg_info *info)
|
||||
* When this happens the AXP288 reports a not-charging status and
|
||||
* 0 mA discharge current.
|
||||
*/
|
||||
if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR))
|
||||
if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR) || no_current_sense_res)
|
||||
goto not_full;
|
||||
|
||||
if (curr == 0) {
|
||||
@ -477,7 +490,9 @@ static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
|
||||
dev_warn(info->dev, "Spurious Interrupt!!!\n");
|
||||
}
|
||||
|
||||
mutex_lock(&info->lock);
|
||||
info->valid = 0; /* Force updating of the cached registers */
|
||||
mutex_unlock(&info->lock);
|
||||
|
||||
power_supply_changed(info->bat);
|
||||
return IRQ_HANDLED;
|
||||
@ -487,11 +502,13 @@ static void fuel_gauge_external_power_changed(struct power_supply *psy)
|
||||
{
|
||||
struct axp288_fg_info *info = power_supply_get_drvdata(psy);
|
||||
|
||||
mutex_lock(&info->lock);
|
||||
info->valid = 0; /* Force updating of the cached registers */
|
||||
mutex_unlock(&info->lock);
|
||||
power_supply_changed(info->bat);
|
||||
}
|
||||
|
||||
static const struct power_supply_desc fuel_gauge_desc = {
|
||||
static struct power_supply_desc fuel_gauge_desc = {
|
||||
.name = DEV_NAME,
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.properties = fuel_gauge_props,
|
||||
@ -502,38 +519,6 @@ static const struct power_supply_desc fuel_gauge_desc = {
|
||||
.external_power_changed = fuel_gauge_external_power_changed,
|
||||
};
|
||||
|
||||
static void fuel_gauge_init_irq(struct axp288_fg_info *info, struct platform_device *pdev)
|
||||
{
|
||||
int ret, i, pirq;
|
||||
|
||||
for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
|
||||
pirq = platform_get_irq(pdev, i);
|
||||
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
|
||||
if (info->irq[i] < 0) {
|
||||
dev_warn(info->dev, "regmap_irq get virq failed for IRQ %d: %d\n",
|
||||
pirq, info->irq[i]);
|
||||
info->irq[i] = -1;
|
||||
goto intr_failed;
|
||||
}
|
||||
ret = request_threaded_irq(info->irq[i],
|
||||
NULL, fuel_gauge_thread_handler,
|
||||
IRQF_ONESHOT, DEV_NAME, info);
|
||||
if (ret) {
|
||||
dev_warn(info->dev, "request irq failed for IRQ %d: %d\n",
|
||||
pirq, info->irq[i]);
|
||||
info->irq[i] = -1;
|
||||
goto intr_failed;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
intr_failed:
|
||||
for (; i > 0; i--) {
|
||||
free_irq(info->irq[i - 1], info);
|
||||
info->irq[i - 1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Some devices have no battery (HDMI sticks) and the axp288 battery's
|
||||
* detection reports one despite it not being there.
|
||||
@ -560,12 +545,6 @@ static const struct dmi_system_id axp288_no_battery_list[] = {
|
||||
DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* ECS EF20EA */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "EF20EA"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Intel Cherry Trail Compute Stick, Windows version */
|
||||
.matches = {
|
||||
@ -611,85 +590,33 @@ static const struct dmi_system_id axp288_no_battery_list[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
static int axp288_fuel_gauge_probe(struct platform_device *pdev)
|
||||
static int axp288_fuel_gauge_read_initial_regs(struct axp288_fg_info *info)
|
||||
{
|
||||
int i, ret = 0;
|
||||
struct axp288_fg_info *info;
|
||||
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
||||
struct power_supply_config psy_cfg = {};
|
||||
static const char * const iio_chan_name[] = {
|
||||
[BAT_CHRG_CURR] = "axp288-chrg-curr",
|
||||
[BAT_D_CURR] = "axp288-chrg-d-curr",
|
||||
[BAT_VOLT] = "axp288-batt-volt",
|
||||
};
|
||||
unsigned int val;
|
||||
|
||||
if (dmi_check_system(axp288_no_battery_list))
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->dev = &pdev->dev;
|
||||
info->regmap = axp20x->regmap;
|
||||
info->regmap_irqc = axp20x->regmap_irqc;
|
||||
info->status = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
info->valid = 0;
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
mutex_init(&info->lock);
|
||||
|
||||
for (i = 0; i < IIO_CHANNEL_NUM; i++) {
|
||||
/*
|
||||
* Note cannot use devm_iio_channel_get because x86 systems
|
||||
* lack the device<->channel maps which iio_channel_get will
|
||||
* try to use when passed a non NULL device pointer.
|
||||
*/
|
||||
info->iio_channel[i] =
|
||||
iio_channel_get(NULL, iio_chan_name[i]);
|
||||
if (IS_ERR(info->iio_channel[i])) {
|
||||
ret = PTR_ERR(info->iio_channel[i]);
|
||||
dev_dbg(&pdev->dev, "error getting iiochan %s: %d\n",
|
||||
iio_chan_name[i], ret);
|
||||
/* Wait for axp288_adc to load */
|
||||
if (ret == -ENODEV)
|
||||
ret = -EPROBE_DEFER;
|
||||
|
||||
goto out_free_iio_chan;
|
||||
}
|
||||
}
|
||||
|
||||
ret = iosf_mbi_block_punit_i2c_access();
|
||||
if (ret < 0)
|
||||
goto out_free_iio_chan;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* On some devices the fuelgauge and charger parts of the axp288 are
|
||||
* not used, check that the fuelgauge is enabled (CC_CTRL != 0).
|
||||
*/
|
||||
ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val);
|
||||
ret = regmap_read(info->regmap, AXP20X_CC_CTRL, &val);
|
||||
if (ret < 0)
|
||||
goto unblock_punit_i2c_access;
|
||||
if (val == 0) {
|
||||
ret = -ENODEV;
|
||||
goto unblock_punit_i2c_access;
|
||||
}
|
||||
return ret;
|
||||
if (val == 0)
|
||||
return -ENODEV;
|
||||
|
||||
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
|
||||
if (ret < 0)
|
||||
goto unblock_punit_i2c_access;
|
||||
return ret;
|
||||
|
||||
if (!(ret & FG_DES_CAP1_VALID)) {
|
||||
dev_err(&pdev->dev, "axp288 not configured by firmware\n");
|
||||
ret = -ENODEV;
|
||||
goto unblock_punit_i2c_access;
|
||||
dev_err(info->dev, "axp288 not configured by firmware\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
|
||||
if (ret < 0)
|
||||
goto unblock_punit_i2c_access;
|
||||
return ret;
|
||||
switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
|
||||
case CHRG_CCCV_CV_4100MV:
|
||||
info->max_volt = 4100;
|
||||
@ -707,38 +634,124 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
|
||||
|
||||
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
|
||||
if (ret < 0)
|
||||
goto unblock_punit_i2c_access;
|
||||
return ret;
|
||||
info->pwr_op = ret;
|
||||
|
||||
ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
|
||||
if (ret < 0)
|
||||
goto unblock_punit_i2c_access;
|
||||
return ret;
|
||||
info->low_cap = ret;
|
||||
|
||||
unblock_punit_i2c_access:
|
||||
iosf_mbi_unblock_punit_i2c_access();
|
||||
/* In case we arrive here by goto because of a register access error */
|
||||
if (ret < 0)
|
||||
goto out_free_iio_chan;
|
||||
|
||||
psy_cfg.drv_data = info;
|
||||
info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
|
||||
if (IS_ERR(info->bat)) {
|
||||
ret = PTR_ERR(info->bat);
|
||||
dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
|
||||
goto out_free_iio_chan;
|
||||
}
|
||||
|
||||
fuel_gauge_init_irq(info, pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void axp288_fuel_gauge_release_iio_chans(void *data)
|
||||
{
|
||||
struct axp288_fg_info *info = data;
|
||||
int i;
|
||||
|
||||
out_free_iio_chan:
|
||||
for (i = 0; i < IIO_CHANNEL_NUM; i++)
|
||||
if (!IS_ERR_OR_NULL(info->iio_channel[i]))
|
||||
iio_channel_release(info->iio_channel[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
static int axp288_fuel_gauge_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct axp288_fg_info *info;
|
||||
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
||||
struct power_supply_config psy_cfg = {};
|
||||
static const char * const iio_chan_name[] = {
|
||||
[BAT_CHRG_CURR] = "axp288-chrg-curr",
|
||||
[BAT_D_CURR] = "axp288-chrg-d-curr",
|
||||
[BAT_VOLT] = "axp288-batt-volt",
|
||||
};
|
||||
struct device *dev = &pdev->dev;
|
||||
int i, pirq, ret;
|
||||
|
||||
/*
|
||||
* Normally the native AXP288 fg/charger drivers are preferred but
|
||||
* on some devices the ACPI drivers should be used instead.
|
||||
*/
|
||||
if (!acpi_quirk_skip_acpi_ac_and_battery())
|
||||
return -ENODEV;
|
||||
|
||||
if (dmi_check_system(axp288_no_battery_list))
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->dev = dev;
|
||||
info->regmap = axp20x->regmap;
|
||||
info->status = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
info->valid = 0;
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
mutex_init(&info->lock);
|
||||
|
||||
for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
|
||||
pirq = platform_get_irq(pdev, i);
|
||||
ret = regmap_irq_get_virq(axp20x->regmap_irqc, pirq);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "getting vIRQ %d\n", pirq);
|
||||
|
||||
info->irq[i] = ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < IIO_CHANNEL_NUM; i++) {
|
||||
/*
|
||||
* Note cannot use devm_iio_channel_get because x86 systems
|
||||
* lack the device<->channel maps which iio_channel_get will
|
||||
* try to use when passed a non NULL device pointer.
|
||||
*/
|
||||
info->iio_channel[i] =
|
||||
iio_channel_get(NULL, iio_chan_name[i]);
|
||||
if (IS_ERR(info->iio_channel[i])) {
|
||||
ret = PTR_ERR(info->iio_channel[i]);
|
||||
dev_dbg(dev, "error getting iiochan %s: %d\n", iio_chan_name[i], ret);
|
||||
/* Wait for axp288_adc to load */
|
||||
if (ret == -ENODEV)
|
||||
ret = -EPROBE_DEFER;
|
||||
|
||||
axp288_fuel_gauge_release_iio_chans(info);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, axp288_fuel_gauge_release_iio_chans, info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iosf_mbi_block_punit_i2c_access();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = axp288_fuel_gauge_read_initial_regs(info);
|
||||
iosf_mbi_unblock_punit_i2c_access();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
psy_cfg.drv_data = info;
|
||||
if (no_current_sense_res)
|
||||
fuel_gauge_desc.num_properties = ARRAY_SIZE(fuel_gauge_props) - 3;
|
||||
info->bat = devm_power_supply_register(dev, &fuel_gauge_desc, &psy_cfg);
|
||||
if (IS_ERR(info->bat)) {
|
||||
ret = PTR_ERR(info->bat);
|
||||
dev_err(dev, "failed to register battery: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
|
||||
ret = devm_request_threaded_irq(dev, info->irq[i], NULL,
|
||||
fuel_gauge_thread_handler,
|
||||
IRQF_ONESHOT, DEV_NAME, info);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "requesting IRQ %d\n", info->irq[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id axp288_fg_id_table[] = {
|
||||
@ -747,26 +760,8 @@ static const struct platform_device_id axp288_fg_id_table[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, axp288_fg_id_table);
|
||||
|
||||
static int axp288_fuel_gauge_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct axp288_fg_info *info = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
power_supply_unregister(info->bat);
|
||||
|
||||
for (i = 0; i < AXP288_FG_INTR_NUM; i++)
|
||||
if (info->irq[i] >= 0)
|
||||
free_irq(info->irq[i], info);
|
||||
|
||||
for (i = 0; i < IIO_CHANNEL_NUM; i++)
|
||||
iio_channel_release(info->iio_channel[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver axp288_fuel_gauge_driver = {
|
||||
.probe = axp288_fuel_gauge_probe,
|
||||
.remove = axp288_fuel_gauge_remove,
|
||||
.id_table = axp288_fg_id_table,
|
||||
.driver = {
|
||||
.name = DEV_NAME,
|
||||
|
@ -39,6 +39,7 @@
|
||||
#define BQ24190_REG_POC_CHG_CONFIG_DISABLE 0x0
|
||||
#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1
|
||||
#define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2
|
||||
#define BQ24190_REG_POC_CHG_CONFIG_OTG_ALT 0x3
|
||||
#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1))
|
||||
#define BQ24190_REG_POC_SYS_MIN_SHIFT 1
|
||||
#define BQ24190_REG_POC_SYS_MIN_MIN 3000
|
||||
@ -162,15 +163,24 @@ struct bq24190_dev_info {
|
||||
char model_name[I2C_NAME_SIZE];
|
||||
bool initialized;
|
||||
bool irq_event;
|
||||
bool otg_vbus_enabled;
|
||||
int charge_type;
|
||||
u16 sys_min;
|
||||
u16 iprechg;
|
||||
u16 iterm;
|
||||
u32 ichg;
|
||||
u32 ichg_max;
|
||||
u32 vreg;
|
||||
u32 vreg_max;
|
||||
struct mutex f_reg_lock;
|
||||
u8 f_reg;
|
||||
u8 ss_reg;
|
||||
u8 watchdog;
|
||||
};
|
||||
|
||||
static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
|
||||
const union power_supply_propval *val);
|
||||
|
||||
static const unsigned int bq24190_usb_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_NONE,
|
||||
@ -497,10 +507,9 @@ static ssize_t bq24190_sysfs_store(struct device *dev,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_REGULATOR
|
||||
static int bq24190_set_charge_mode(struct regulator_dev *dev, u8 val)
|
||||
static int bq24190_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable)
|
||||
{
|
||||
struct bq24190_dev_info *bdi = rdev_get_drvdata(dev);
|
||||
union power_supply_propval val = { .intval = bdi->charge_type };
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(bdi->dev);
|
||||
@ -510,9 +519,14 @@ static int bq24190_set_charge_mode(struct regulator_dev *dev, u8 val)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
|
||||
BQ24190_REG_POC_CHG_CONFIG_MASK,
|
||||
BQ24190_REG_POC_CHG_CONFIG_SHIFT, val);
|
||||
bdi->otg_vbus_enabled = enable;
|
||||
if (enable)
|
||||
ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
|
||||
BQ24190_REG_POC_CHG_CONFIG_MASK,
|
||||
BQ24190_REG_POC_CHG_CONFIG_SHIFT,
|
||||
BQ24190_REG_POC_CHG_CONFIG_OTG);
|
||||
else
|
||||
ret = bq24190_charger_set_charge_type(bdi, &val);
|
||||
|
||||
pm_runtime_mark_last_busy(bdi->dev);
|
||||
pm_runtime_put_autosuspend(bdi->dev);
|
||||
@ -520,14 +534,15 @@ static int bq24190_set_charge_mode(struct regulator_dev *dev, u8 val)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_REGULATOR
|
||||
static int bq24190_vbus_enable(struct regulator_dev *dev)
|
||||
{
|
||||
return bq24190_set_charge_mode(dev, BQ24190_REG_POC_CHG_CONFIG_OTG);
|
||||
return bq24190_set_otg_vbus(rdev_get_drvdata(dev), true);
|
||||
}
|
||||
|
||||
static int bq24190_vbus_disable(struct regulator_dev *dev)
|
||||
{
|
||||
return bq24190_set_charge_mode(dev, BQ24190_REG_POC_CHG_CONFIG_CHARGE);
|
||||
return bq24190_set_otg_vbus(rdev_get_drvdata(dev), false);
|
||||
}
|
||||
|
||||
static int bq24190_vbus_is_enabled(struct regulator_dev *dev)
|
||||
@ -550,7 +565,12 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev)
|
||||
pm_runtime_mark_last_busy(bdi->dev);
|
||||
pm_runtime_put_autosuspend(bdi->dev);
|
||||
|
||||
return ret ? ret : val == BQ24190_REG_POC_CHG_CONFIG_OTG;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bdi->otg_vbus_enabled = (val == BQ24190_REG_POC_CHG_CONFIG_OTG ||
|
||||
val == BQ24190_REG_POC_CHG_CONFIG_OTG_ALT);
|
||||
return bdi->otg_vbus_enabled;
|
||||
}
|
||||
|
||||
static const struct regulator_ops bq24190_vbus_ops = {
|
||||
@ -659,6 +679,28 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bdi->ichg) {
|
||||
ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC,
|
||||
BQ24190_REG_CCC_ICHG_MASK,
|
||||
BQ24190_REG_CCC_ICHG_SHIFT,
|
||||
bq24190_ccc_ichg_values,
|
||||
ARRAY_SIZE(bq24190_ccc_ichg_values),
|
||||
bdi->ichg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bdi->vreg) {
|
||||
ret = bq24190_set_field_val(bdi, BQ24190_REG_CVC,
|
||||
BQ24190_REG_CVC_VREG_MASK,
|
||||
BQ24190_REG_CVC_VREG_SHIFT,
|
||||
bq24190_cvc_vreg_values,
|
||||
ARRAY_SIZE(bq24190_cvc_vreg_values),
|
||||
bdi->vreg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -775,6 +817,14 @@ static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bdi->charge_type = val->intval;
|
||||
/*
|
||||
* If the 5V Vbus boost regulator is enabled delay setting
|
||||
* the charge-type until its gets disabled.
|
||||
*/
|
||||
if (bdi->otg_vbus_enabled)
|
||||
return 0;
|
||||
|
||||
if (chg_config) { /* Enabling the charger */
|
||||
ret = bq24190_write_mask(bdi, BQ24190_REG_CCC,
|
||||
BQ24190_REG_CCC_FORCE_20PCT_MASK,
|
||||
@ -976,15 +1026,6 @@ static int bq24190_charger_get_current(struct bq24190_dev_info *bdi,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
|
||||
|
||||
val->intval = bq24190_ccc_ichg_values[idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
@ -1001,10 +1042,19 @@ static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
|
||||
if (v)
|
||||
curr *= 5;
|
||||
|
||||
return bq24190_set_field_val(bdi, BQ24190_REG_CCC,
|
||||
if (curr > bdi->ichg_max)
|
||||
return -EINVAL;
|
||||
|
||||
ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC,
|
||||
BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
|
||||
bq24190_ccc_ichg_values,
|
||||
ARRAY_SIZE(bq24190_ccc_ichg_values), curr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bdi->ichg = curr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
|
||||
@ -1023,22 +1073,24 @@ static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
|
||||
|
||||
val->intval = bq24190_cvc_vreg_values[idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
return bq24190_set_field_val(bdi, BQ24190_REG_CVC,
|
||||
int ret;
|
||||
|
||||
if (val->intval > bdi->vreg_max)
|
||||
return -EINVAL;
|
||||
|
||||
ret = bq24190_set_field_val(bdi, BQ24190_REG_CVC,
|
||||
BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
|
||||
bq24190_cvc_vreg_values,
|
||||
ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bdi->vreg = val->intval;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24190_charger_get_iinlimit(struct bq24190_dev_info *bdi,
|
||||
@ -1108,13 +1160,15 @@ static int bq24190_charger_get_property(struct power_supply *psy,
|
||||
ret = bq24190_charger_get_current(bdi, val);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
ret = bq24190_charger_get_current_max(bdi, val);
|
||||
val->intval = bdi->ichg_max;
|
||||
ret = 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
ret = bq24190_charger_get_voltage(bdi, val);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
ret = bq24190_charger_get_voltage_max(bdi, val);
|
||||
val->intval = bdi->vreg_max;
|
||||
ret = 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
ret = bq24190_charger_get_iinlimit(bdi, val);
|
||||
@ -1206,8 +1260,18 @@ static void bq24190_input_current_limit_work(struct work_struct *work)
|
||||
struct bq24190_dev_info *bdi =
|
||||
container_of(work, struct bq24190_dev_info,
|
||||
input_current_limit_work.work);
|
||||
union power_supply_propval val;
|
||||
int ret;
|
||||
|
||||
power_supply_set_input_current_limit_from_supplier(bdi->charger);
|
||||
ret = power_supply_get_property_from_supplier(bdi->charger,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
&val);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
bq24190_charger_set_property(bdi->charger,
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
||||
&val);
|
||||
}
|
||||
|
||||
/* Sync the input-current-limit with our parent supply (if we have one) */
|
||||
@ -1671,7 +1735,13 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi)
|
||||
{
|
||||
const char * const s = "ti,system-minimum-microvolt";
|
||||
struct power_supply_battery_info *info;
|
||||
int v;
|
||||
int v, idx;
|
||||
|
||||
idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
|
||||
bdi->ichg_max = bq24190_ccc_ichg_values[idx];
|
||||
|
||||
idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
|
||||
bdi->vreg_max = bq24190_cvc_vreg_values[idx];
|
||||
|
||||
if (device_property_read_u32(bdi->dev, s, &v) == 0) {
|
||||
v /= 1000;
|
||||
@ -1682,8 +1752,7 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi)
|
||||
dev_warn(bdi->dev, "invalid value for %s: %u\n", s, v);
|
||||
}
|
||||
|
||||
if (bdi->dev->of_node &&
|
||||
!power_supply_get_battery_info(bdi->charger, &info)) {
|
||||
if (!power_supply_get_battery_info(bdi->charger, &info)) {
|
||||
v = info->precharge_current_ua / 1000;
|
||||
if (v >= BQ24190_REG_PCTCC_IPRECHG_MIN
|
||||
&& v <= BQ24190_REG_PCTCC_IPRECHG_MAX)
|
||||
@ -1699,6 +1768,15 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi)
|
||||
else
|
||||
dev_warn(bdi->dev, "invalid value for battery:charge-term-current-microamp: %d\n",
|
||||
v);
|
||||
|
||||
/* These are optional, so no warning when not set */
|
||||
v = info->constant_charge_current_max_ua;
|
||||
if (v >= bq24190_ccc_ichg_values[0] && v <= bdi->ichg_max)
|
||||
bdi->ichg = bdi->ichg_max = v;
|
||||
|
||||
v = info->constant_charge_voltage_max_uv;
|
||||
if (v >= bq24190_cvc_vreg_values[0] && v <= bdi->vreg_max)
|
||||
bdi->vreg = bdi->vreg_max = v;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1728,6 +1806,7 @@ static int bq24190_probe(struct i2c_client *client,
|
||||
bdi->dev = dev;
|
||||
strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
|
||||
mutex_init(&bdi->f_reg_lock);
|
||||
bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||||
bdi->f_reg = 0;
|
||||
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
|
||||
INIT_DELAYED_WORK(&bdi->input_current_limit_work,
|
||||
@ -1860,6 +1939,14 @@ static int bq24190_remove(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bq24190_shutdown(struct i2c_client *client)
|
||||
{
|
||||
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
|
||||
|
||||
/* Turn off 5V boost regulator on shutdown */
|
||||
bq24190_set_otg_vbus(bdi, false);
|
||||
}
|
||||
|
||||
static __maybe_unused int bq24190_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
@ -1970,6 +2057,7 @@ MODULE_DEVICE_TABLE(of, bq24190_of_match);
|
||||
static struct i2c_driver bq24190_driver = {
|
||||
.probe = bq24190_probe,
|
||||
.remove = bq24190_remove,
|
||||
.shutdown = bq24190_shutdown,
|
||||
.id_table = bq24190_i2c_ids,
|
||||
.driver = {
|
||||
.name = "bq24190-charger",
|
||||
|
@ -8,7 +8,9 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/power/bq25890_charger.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -25,6 +27,10 @@
|
||||
#define BQ25895_ID 7
|
||||
#define BQ25896_ID 0
|
||||
|
||||
#define PUMP_EXPRESS_START_DELAY (5 * HZ)
|
||||
#define PUMP_EXPRESS_MAX_TRIES 6
|
||||
#define PUMP_EXPRESS_VBUS_MARGIN_uV 1000000
|
||||
|
||||
enum bq25890_chip_version {
|
||||
BQ25890,
|
||||
BQ25892,
|
||||
@ -40,7 +46,7 @@ static const char *const bq25890_chip_name[] = {
|
||||
};
|
||||
|
||||
enum bq25890_fields {
|
||||
F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
|
||||
F_EN_HIZ, F_EN_ILIM, F_IINLIM, /* Reg00 */
|
||||
F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
|
||||
F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
|
||||
F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */
|
||||
@ -94,6 +100,7 @@ struct bq25890_state {
|
||||
u8 vsys_status;
|
||||
u8 boost_fault;
|
||||
u8 bat_fault;
|
||||
u8 ntc_fault;
|
||||
};
|
||||
|
||||
struct bq25890_device {
|
||||
@ -104,11 +111,15 @@ struct bq25890_device {
|
||||
struct usb_phy *usb_phy;
|
||||
struct notifier_block usb_nb;
|
||||
struct work_struct usb_work;
|
||||
struct delayed_work pump_express_work;
|
||||
unsigned long usb_event;
|
||||
|
||||
struct regmap *rmap;
|
||||
struct regmap_field *rmap_fields[F_MAX_FIELDS];
|
||||
|
||||
bool skip_reset;
|
||||
bool read_back_init_data;
|
||||
u32 pump_express_vbus_max;
|
||||
enum bq25890_chip_version chip_version;
|
||||
struct bq25890_init_data init_data;
|
||||
struct bq25890_state state;
|
||||
@ -153,7 +164,7 @@ static const struct reg_field bq25890_reg_fields[] = {
|
||||
/* REG00 */
|
||||
[F_EN_HIZ] = REG_FIELD(0x00, 7, 7),
|
||||
[F_EN_ILIM] = REG_FIELD(0x00, 6, 6),
|
||||
[F_IILIM] = REG_FIELD(0x00, 0, 5),
|
||||
[F_IINLIM] = REG_FIELD(0x00, 0, 5),
|
||||
/* REG01 */
|
||||
[F_BHOT] = REG_FIELD(0x01, 6, 7),
|
||||
[F_BCOLD] = REG_FIELD(0x01, 5, 5),
|
||||
@ -256,10 +267,11 @@ enum bq25890_table_ids {
|
||||
/* range tables */
|
||||
TBL_ICHG,
|
||||
TBL_ITERM,
|
||||
TBL_IILIM,
|
||||
TBL_IINLIM,
|
||||
TBL_VREG,
|
||||
TBL_BOOSTV,
|
||||
TBL_SYSVMIN,
|
||||
TBL_VBUSV,
|
||||
TBL_VBATCOMP,
|
||||
TBL_RBATCOMP,
|
||||
|
||||
@ -320,14 +332,15 @@ static const union {
|
||||
} bq25890_tables[] = {
|
||||
/* range tables */
|
||||
/* TODO: BQ25896 has max ICHG 3008 mA */
|
||||
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
|
||||
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
|
||||
[TBL_IILIM] = { .rt = {100000, 3250000, 50000} }, /* uA */
|
||||
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
|
||||
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
|
||||
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
|
||||
[TBL_VBATCOMP] ={ .rt = {0, 224000, 32000} }, /* uV */
|
||||
[TBL_RBATCOMP] ={ .rt = {0, 140000, 20000} }, /* uOhm */
|
||||
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
|
||||
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
|
||||
[TBL_IINLIM] = { .rt = {100000, 3250000, 50000} }, /* uA */
|
||||
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
|
||||
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
|
||||
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
|
||||
[TBL_VBUSV] = { .rt = {2600000, 15300000, 100000} }, /* uV */
|
||||
[TBL_VBATCOMP] = { .rt = {0, 224000, 32000} }, /* uV */
|
||||
[TBL_RBATCOMP] = { .rt = {0, 140000, 20000} }, /* uOhm */
|
||||
|
||||
/* lookup tables */
|
||||
[TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
|
||||
@ -407,6 +420,14 @@ enum bq25890_chrg_fault {
|
||||
CHRG_FAULT_TIMER_EXPIRED,
|
||||
};
|
||||
|
||||
enum bq25890_ntc_fault {
|
||||
NTC_FAULT_NORMAL = 0,
|
||||
NTC_FAULT_WARM = 2,
|
||||
NTC_FAULT_COOL = 3,
|
||||
NTC_FAULT_COLD = 5,
|
||||
NTC_FAULT_HOT = 6,
|
||||
};
|
||||
|
||||
static bool bq25890_is_adc_property(enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
@ -422,6 +443,17 @@ static bool bq25890_is_adc_property(enum power_supply_property psp)
|
||||
|
||||
static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq);
|
||||
|
||||
static int bq25890_get_vbus_voltage(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bq25890_field_read(bq, F_VBUSV);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return bq25890_find_val(ret, TBL_VBUSV);
|
||||
}
|
||||
|
||||
static int bq25890_power_supply_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
@ -499,6 +531,18 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = bq25890_find_val(bq->init_data.ichg, TBL_ICHG);
|
||||
|
||||
/* When temperature is too low, charge current is decreased */
|
||||
if (bq->state.ntc_fault == NTC_FAULT_COOL) {
|
||||
ret = bq25890_field_read(bq, F_JEITA_ISET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret)
|
||||
val->intval /= 5;
|
||||
else
|
||||
val->intval /= 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
@ -528,11 +572,11 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
ret = bq25890_field_read(bq, F_IILIM);
|
||||
ret = bq25890_field_read(bq, F_IINLIM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val->intval = bq25890_find_val(ret, TBL_IILIM);
|
||||
val->intval = bq25890_find_val(ret, TBL_IINLIM);
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
@ -569,6 +613,43 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* On the BQ25892 try to get charger-type info from our supplier */
|
||||
static void bq25890_charger_external_power_changed(struct power_supply *psy)
|
||||
{
|
||||
struct bq25890_device *bq = power_supply_get_drvdata(psy);
|
||||
union power_supply_propval val;
|
||||
int input_current_limit, ret;
|
||||
|
||||
if (bq->chip_version != BQ25892)
|
||||
return;
|
||||
|
||||
ret = power_supply_get_property_from_supplier(bq->charger,
|
||||
POWER_SUPPLY_PROP_USB_TYPE,
|
||||
&val);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
switch (val.intval) {
|
||||
case POWER_SUPPLY_USB_TYPE_DCP:
|
||||
input_current_limit = bq25890_find_idx(2000000, TBL_IINLIM);
|
||||
if (bq->pump_express_vbus_max) {
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&bq->pump_express_work,
|
||||
PUMP_EXPRESS_START_DELAY);
|
||||
}
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_CDP:
|
||||
case POWER_SUPPLY_USB_TYPE_ACA:
|
||||
input_current_limit = bq25890_find_idx(1500000, TBL_IINLIM);
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_SDP:
|
||||
default:
|
||||
input_current_limit = bq25890_find_idx(500000, TBL_IINLIM);
|
||||
}
|
||||
|
||||
bq25890_field_write(bq, F_IINLIM, input_current_limit);
|
||||
}
|
||||
|
||||
static int bq25890_get_chip_state(struct bq25890_device *bq,
|
||||
struct bq25890_state *state)
|
||||
{
|
||||
@ -583,7 +664,8 @@ static int bq25890_get_chip_state(struct bq25890_device *bq,
|
||||
{F_VSYS_STAT, &state->vsys_status},
|
||||
{F_BOOST_FAULT, &state->boost_fault},
|
||||
{F_BAT_FAULT, &state->bat_fault},
|
||||
{F_CHG_FAULT, &state->chrg_fault}
|
||||
{F_CHG_FAULT, &state->chrg_fault},
|
||||
{F_NTC_FAULT, &state->ntc_fault}
|
||||
};
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
|
||||
@ -594,9 +676,10 @@ static int bq25890_get_chip_state(struct bq25890_device *bq,
|
||||
*state_fields[i].data = ret;
|
||||
}
|
||||
|
||||
dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n",
|
||||
dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT/NTC=%d/%d/%d/%d\n",
|
||||
state->chrg_status, state->online, state->vsys_status,
|
||||
state->chrg_fault, state->boost_fault, state->bat_fault);
|
||||
state->chrg_fault, state->boost_fault, state->bat_fault,
|
||||
state->ntc_fault);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -670,33 +753,69 @@ static int bq25890_chip_reset(struct bq25890_device *bq)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_hw_init(struct bq25890_device *bq)
|
||||
static int bq25890_rw_init_data(struct bq25890_device *bq)
|
||||
{
|
||||
bool write = !bq->read_back_init_data;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
const struct {
|
||||
enum bq25890_fields id;
|
||||
u32 value;
|
||||
u8 *value;
|
||||
} init_data[] = {
|
||||
{F_ICHG, bq->init_data.ichg},
|
||||
{F_VREG, bq->init_data.vreg},
|
||||
{F_ITERM, bq->init_data.iterm},
|
||||
{F_IPRECHG, bq->init_data.iprechg},
|
||||
{F_SYSVMIN, bq->init_data.sysvmin},
|
||||
{F_BOOSTV, bq->init_data.boostv},
|
||||
{F_BOOSTI, bq->init_data.boosti},
|
||||
{F_BOOSTF, bq->init_data.boostf},
|
||||
{F_EN_ILIM, bq->init_data.ilim_en},
|
||||
{F_TREG, bq->init_data.treg},
|
||||
{F_BATCMP, bq->init_data.rbatcomp},
|
||||
{F_VCLAMP, bq->init_data.vclamp},
|
||||
{F_ICHG, &bq->init_data.ichg},
|
||||
{F_VREG, &bq->init_data.vreg},
|
||||
{F_ITERM, &bq->init_data.iterm},
|
||||
{F_IPRECHG, &bq->init_data.iprechg},
|
||||
{F_SYSVMIN, &bq->init_data.sysvmin},
|
||||
{F_BOOSTV, &bq->init_data.boostv},
|
||||
{F_BOOSTI, &bq->init_data.boosti},
|
||||
{F_BOOSTF, &bq->init_data.boostf},
|
||||
{F_EN_ILIM, &bq->init_data.ilim_en},
|
||||
{F_TREG, &bq->init_data.treg},
|
||||
{F_BATCMP, &bq->init_data.rbatcomp},
|
||||
{F_VCLAMP, &bq->init_data.vclamp},
|
||||
};
|
||||
|
||||
ret = bq25890_chip_reset(bq);
|
||||
if (ret < 0) {
|
||||
dev_dbg(bq->dev, "Reset failed %d\n", ret);
|
||||
return ret;
|
||||
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
||||
if (write) {
|
||||
ret = bq25890_field_write(bq, init_data[i].id,
|
||||
*init_data[i].value);
|
||||
} else {
|
||||
ret = bq25890_field_read(bq, init_data[i].id);
|
||||
if (ret >= 0)
|
||||
*init_data[i].value = ret;
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_dbg(bq->dev, "Accessing init data failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_hw_init(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!bq->skip_reset) {
|
||||
ret = bq25890_chip_reset(bq);
|
||||
if (ret < 0) {
|
||||
dev_dbg(bq->dev, "Reset failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Ensure charging is enabled, on some boards where the fw
|
||||
* takes care of initalizition F_CHG_CFG is set to 0 before
|
||||
* handing control over to the OS.
|
||||
*/
|
||||
ret = bq25890_field_write(bq, F_CHG_CFG, 1);
|
||||
if (ret < 0) {
|
||||
dev_dbg(bq->dev, "Enabling charging failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* disable watchdog */
|
||||
@ -707,14 +826,9 @@ static int bq25890_hw_init(struct bq25890_device *bq)
|
||||
}
|
||||
|
||||
/* initialize currents/voltages and other parameters */
|
||||
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
||||
ret = bq25890_field_write(bq, init_data[i].id,
|
||||
init_data[i].value);
|
||||
if (ret < 0) {
|
||||
dev_dbg(bq->dev, "Writing init data failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = bq25890_rw_init_data(bq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bq25890_get_chip_state(bq, &bq->state);
|
||||
if (ret < 0) {
|
||||
@ -760,6 +874,7 @@ static const struct power_supply_desc bq25890_power_supply_desc = {
|
||||
.properties = bq25890_power_supply_props,
|
||||
.num_properties = ARRAY_SIZE(bq25890_power_supply_props),
|
||||
.get_property = bq25890_power_supply_get_property,
|
||||
.external_power_changed = bq25890_charger_external_power_changed,
|
||||
};
|
||||
|
||||
static int bq25890_power_supply_init(struct bq25890_device *bq)
|
||||
@ -776,6 +891,64 @@ static int bq25890_power_supply_init(struct bq25890_device *bq)
|
||||
return PTR_ERR_OR_ZERO(bq->charger);
|
||||
}
|
||||
|
||||
static int bq25890_set_otg_cfg(struct bq25890_device *bq, u8 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bq25890_field_write(bq, F_OTG_CFG, val);
|
||||
if (ret < 0)
|
||||
dev_err(bq->dev, "Error switching to boost/charger mode: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bq25890_pump_express_work(struct work_struct *data)
|
||||
{
|
||||
struct bq25890_device *bq =
|
||||
container_of(data, struct bq25890_device, pump_express_work.work);
|
||||
int voltage, i, ret;
|
||||
|
||||
dev_dbg(bq->dev, "Start to request input voltage increasing\n");
|
||||
|
||||
/* Enable current pulse voltage control protocol */
|
||||
ret = bq25890_field_write(bq, F_PUMPX_EN, 1);
|
||||
if (ret < 0)
|
||||
goto error_print;
|
||||
|
||||
for (i = 0; i < PUMP_EXPRESS_MAX_TRIES; i++) {
|
||||
voltage = bq25890_get_vbus_voltage(bq);
|
||||
if (voltage < 0)
|
||||
goto error_print;
|
||||
dev_dbg(bq->dev, "input voltage = %d uV\n", voltage);
|
||||
|
||||
if ((voltage + PUMP_EXPRESS_VBUS_MARGIN_uV) >
|
||||
bq->pump_express_vbus_max)
|
||||
break;
|
||||
|
||||
ret = bq25890_field_write(bq, F_PUMPX_UP, 1);
|
||||
if (ret < 0)
|
||||
goto error_print;
|
||||
|
||||
/* Note a single PUMPX up pulse-sequence takes 2.1s */
|
||||
ret = regmap_field_read_poll_timeout(bq->rmap_fields[F_PUMPX_UP],
|
||||
ret, !ret, 100000, 3000000);
|
||||
if (ret < 0)
|
||||
goto error_print;
|
||||
|
||||
/* Make sure ADC has sampled Vbus before checking again */
|
||||
msleep(1000);
|
||||
}
|
||||
|
||||
bq25890_field_write(bq, F_PUMPX_EN, 0);
|
||||
|
||||
dev_info(bq->dev, "Hi-voltage charging requested, input voltage is %d mV\n",
|
||||
voltage);
|
||||
|
||||
return;
|
||||
error_print:
|
||||
dev_err(bq->dev, "Failed to request hi-voltage charging\n");
|
||||
}
|
||||
|
||||
static void bq25890_usb_work(struct work_struct *data)
|
||||
{
|
||||
int ret;
|
||||
@ -785,25 +958,16 @@ static void bq25890_usb_work(struct work_struct *data)
|
||||
switch (bq->usb_event) {
|
||||
case USB_EVENT_ID:
|
||||
/* Enable boost mode */
|
||||
ret = bq25890_field_write(bq, F_OTG_CFG, 1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
bq25890_set_otg_cfg(bq, 1);
|
||||
break;
|
||||
|
||||
case USB_EVENT_NONE:
|
||||
/* Disable boost mode */
|
||||
ret = bq25890_field_write(bq, F_OTG_CFG, 0);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
power_supply_changed(bq->charger);
|
||||
ret = bq25890_set_otg_cfg(bq, 0);
|
||||
if (ret == 0)
|
||||
power_supply_changed(bq->charger);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
dev_err(bq->dev, "Error switching to boost/charger mode.\n");
|
||||
}
|
||||
|
||||
static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
|
||||
@ -818,6 +982,45 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_REGULATOR
|
||||
static int bq25890_vbus_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct bq25890_device *bq = rdev_get_drvdata(rdev);
|
||||
|
||||
return bq25890_set_otg_cfg(bq, 1);
|
||||
}
|
||||
|
||||
static int bq25890_vbus_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct bq25890_device *bq = rdev_get_drvdata(rdev);
|
||||
|
||||
return bq25890_set_otg_cfg(bq, 0);
|
||||
}
|
||||
|
||||
static int bq25890_vbus_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct bq25890_device *bq = rdev_get_drvdata(rdev);
|
||||
|
||||
return bq25890_field_read(bq, F_OTG_CFG);
|
||||
}
|
||||
|
||||
static const struct regulator_ops bq25890_vbus_ops = {
|
||||
.enable = bq25890_vbus_enable,
|
||||
.disable = bq25890_vbus_disable,
|
||||
.is_enabled = bq25890_vbus_is_enabled,
|
||||
};
|
||||
|
||||
static const struct regulator_desc bq25890_vbus_desc = {
|
||||
.name = "usb_otg_vbus",
|
||||
.of_match = "usb-otg-vbus",
|
||||
.type = REGULATOR_VOLTAGE,
|
||||
.owner = THIS_MODULE,
|
||||
.ops = &bq25890_vbus_ops,
|
||||
.fixed_uV = 5000000,
|
||||
.n_voltages = 1,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int bq25890_get_chip_version(struct bq25890_device *bq)
|
||||
{
|
||||
int id, rev;
|
||||
@ -936,6 +1139,16 @@ static int bq25890_fw_probe(struct bq25890_device *bq)
|
||||
int ret;
|
||||
struct bq25890_init_data *init = &bq->init_data;
|
||||
|
||||
/* Optional, left at 0 if property is not present */
|
||||
device_property_read_u32(bq->dev, "linux,pump-express-vbus-max",
|
||||
&bq->pump_express_vbus_max);
|
||||
|
||||
bq->skip_reset = device_property_read_bool(bq->dev, "linux,skip-reset");
|
||||
bq->read_back_init_data = device_property_read_bool(bq->dev,
|
||||
"linux,read-back-settings");
|
||||
if (bq->read_back_init_data)
|
||||
return 0;
|
||||
|
||||
ret = bq25890_fw_read_u32_props(bq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@ -952,7 +1165,6 @@ static int bq25890_probe(struct i2c_client *client,
|
||||
struct device *dev = &client->dev;
|
||||
struct bq25890_device *bq;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
|
||||
if (!bq)
|
||||
@ -962,21 +1174,17 @@ static int bq25890_probe(struct i2c_client *client,
|
||||
bq->dev = dev;
|
||||
|
||||
mutex_init(&bq->lock);
|
||||
INIT_DELAYED_WORK(&bq->pump_express_work, bq25890_pump_express_work);
|
||||
|
||||
bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config);
|
||||
if (IS_ERR(bq->rmap))
|
||||
return dev_err_probe(dev, PTR_ERR(bq->rmap),
|
||||
"failed to allocate register map\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) {
|
||||
const struct reg_field *reg_fields = bq25890_reg_fields;
|
||||
|
||||
bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
|
||||
reg_fields[i]);
|
||||
if (IS_ERR(bq->rmap_fields[i]))
|
||||
return dev_err_probe(dev, PTR_ERR(bq->rmap_fields[i]),
|
||||
"cannot allocate regmap field\n");
|
||||
}
|
||||
ret = devm_regmap_field_bulk_alloc(dev, bq->rmap, bq->rmap_fields,
|
||||
bq25890_reg_fields, F_MAX_FIELDS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_set_clientdata(client, bq);
|
||||
|
||||
@ -986,16 +1194,9 @@ static int bq25890_probe(struct i2c_client *client,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!dev->platform_data) {
|
||||
ret = bq25890_fw_probe(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Cannot read device properties: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = bq25890_fw_probe(bq);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "reading device properties\n");
|
||||
|
||||
ret = bq25890_hw_init(bq);
|
||||
if (ret < 0) {
|
||||
@ -1018,6 +1219,22 @@ static int bq25890_probe(struct i2c_client *client,
|
||||
bq->usb_nb.notifier_call = bq25890_usb_notifier;
|
||||
usb_register_notifier(bq->usb_phy, &bq->usb_nb);
|
||||
}
|
||||
#ifdef CONFIG_REGULATOR
|
||||
else {
|
||||
struct bq25890_platform_data *pdata = dev_get_platdata(dev);
|
||||
struct regulator_config cfg = { };
|
||||
struct regulator_dev *reg;
|
||||
|
||||
cfg.dev = dev;
|
||||
cfg.driver_data = bq;
|
||||
if (pdata)
|
||||
cfg.init_data = pdata->regulator_init_data;
|
||||
|
||||
reg = devm_regulator_register(dev, &bq25890_vbus_desc, &cfg);
|
||||
if (IS_ERR(reg))
|
||||
return dev_err_probe(dev, PTR_ERR(reg), "registering regulator");
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = bq25890_power_supply_init(bq);
|
||||
if (ret < 0) {
|
||||
@ -1048,12 +1265,36 @@ static int bq25890_remove(struct i2c_client *client)
|
||||
if (!IS_ERR_OR_NULL(bq->usb_phy))
|
||||
usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
|
||||
|
||||
/* reset all registers to default values */
|
||||
bq25890_chip_reset(bq);
|
||||
if (!bq->skip_reset) {
|
||||
/* reset all registers to default values */
|
||||
bq25890_chip_reset(bq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bq25890_shutdown(struct i2c_client *client)
|
||||
{
|
||||
struct bq25890_device *bq = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
* TODO this if + return should probably be removed, but that would
|
||||
* introduce a function change for boards using the usb-phy framework.
|
||||
* This needs to be tested on such a board before making this change.
|
||||
*/
|
||||
if (!IS_ERR_OR_NULL(bq->usb_phy))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Turn off the 5v Boost regulator which outputs Vbus to the device's
|
||||
* Micro-USB or Type-C USB port. Leaving this on drains power and
|
||||
* this avoids the PMIC on some device-models seeing this as Vbus
|
||||
* getting inserted after shutdown, causing the device to immediately
|
||||
* power-up again.
|
||||
*/
|
||||
bq25890_set_otg_cfg(bq, 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bq25890_suspend(struct device *dev)
|
||||
{
|
||||
@ -1133,6 +1374,7 @@ static struct i2c_driver bq25890_driver = {
|
||||
},
|
||||
.probe = bq25890_probe,
|
||||
.remove = bq25890_remove,
|
||||
.shutdown = bq25890_shutdown,
|
||||
.id_table = bq25890_i2c_ids,
|
||||
};
|
||||
module_i2c_driver(bq25890_driver);
|
||||
|
@ -764,7 +764,7 @@ static int bq25980_get_charger_property(struct power_supply *psy,
|
||||
if (!state.ce)
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||||
else if (state.bypass)
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_BYPASS;
|
||||
else if (!state.bypass)
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
|
||||
break;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/moduleparam.h>
|
||||
|
||||
#include <linux/iio/consumer.h>
|
||||
@ -73,6 +74,9 @@
|
||||
|
||||
#define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250
|
||||
|
||||
#define CPCAP_BATTERY_EB41_HW4X_ID 0x9E
|
||||
#define CPCAP_BATTERY_BW8X_ID 0x98
|
||||
|
||||
enum {
|
||||
CPCAP_BATTERY_IIO_BATTDET,
|
||||
CPCAP_BATTERY_IIO_VOLTAGE,
|
||||
@ -138,6 +142,7 @@ struct cpcap_battery_ddata {
|
||||
int charge_full;
|
||||
int status;
|
||||
u16 vendor;
|
||||
bool check_nvmem;
|
||||
unsigned int is_full:1;
|
||||
};
|
||||
|
||||
@ -354,6 +359,88 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata,
|
||||
ccd->offset);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Based on the values from Motorola mapphone Linux kernel for the
|
||||
* stock Droid 4 battery eb41. In 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. These values are also fine for
|
||||
* Bionic's hw4x.
|
||||
*
|
||||
* 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_eb41_data = {
|
||||
.cd_factor = 0x3cc,
|
||||
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.info.voltage_max_design = 4351000,
|
||||
.info.voltage_min_design = 3100000,
|
||||
.info.charge_full_design = 1740000,
|
||||
.bat.constant_charge_voltage_max_uv = 4200000,
|
||||
};
|
||||
|
||||
/* Values for the extended Droid Bionic battery bw8x. */
|
||||
static const struct cpcap_battery_config cpcap_battery_bw8x_data = {
|
||||
.cd_factor = 0x3cc,
|
||||
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.info.voltage_max_design = 4200000,
|
||||
.info.voltage_min_design = 3200000,
|
||||
.info.charge_full_design = 2760000,
|
||||
.bat.constant_charge_voltage_max_uv = 4200000,
|
||||
};
|
||||
|
||||
/*
|
||||
* Safe values for any lipo battery likely to fit into a mapphone
|
||||
* battery bay.
|
||||
*/
|
||||
static const struct cpcap_battery_config cpcap_battery_unkown_data = {
|
||||
.cd_factor = 0x3cc,
|
||||
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.info.voltage_max_design = 4200000,
|
||||
.info.voltage_min_design = 3200000,
|
||||
.info.charge_full_design = 3000000,
|
||||
.bat.constant_charge_voltage_max_uv = 4200000,
|
||||
};
|
||||
|
||||
static int cpcap_battery_match_nvmem(struct device *dev, const void *data)
|
||||
{
|
||||
if (strcmp(dev_name(dev), "89-500029ba0f73") == 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata)
|
||||
{
|
||||
struct nvmem_device *nvmem;
|
||||
u8 battery_id = 0;
|
||||
|
||||
ddata->check_nvmem = false;
|
||||
|
||||
nvmem = nvmem_device_find(NULL, &cpcap_battery_match_nvmem);
|
||||
if (IS_ERR_OR_NULL(nvmem)) {
|
||||
ddata->check_nvmem = true;
|
||||
dev_info_once(ddata->dev, "Can not find battery nvmem device. Assuming generic lipo battery\n");
|
||||
} else if (nvmem_device_read(nvmem, 2, 1, &battery_id) < 0) {
|
||||
battery_id = 0;
|
||||
ddata->check_nvmem = true;
|
||||
dev_warn(ddata->dev, "Can not read battery nvmem device. Assuming generic lipo battery\n");
|
||||
}
|
||||
|
||||
switch (battery_id) {
|
||||
case CPCAP_BATTERY_EB41_HW4X_ID:
|
||||
ddata->config = cpcap_battery_eb41_data;
|
||||
break;
|
||||
case CPCAP_BATTERY_BW8X_ID:
|
||||
ddata->config = cpcap_battery_bw8x_data;
|
||||
break;
|
||||
default:
|
||||
ddata->config = cpcap_battery_unkown_data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cpcap_battery_cc_get_avg_current - read cpcap coulumb counter
|
||||
* @ddata: cpcap battery driver device data
|
||||
@ -571,6 +658,9 @@ static int cpcap_battery_get_property(struct power_supply *psy,
|
||||
latest = cpcap_battery_latest(ddata);
|
||||
previous = cpcap_battery_previous(ddata);
|
||||
|
||||
if (ddata->check_nvmem)
|
||||
cpcap_battery_detect_battery_type(ddata);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
if (latest->temperature > CPCAP_NO_BATTERY || ignore_temperature_probe)
|
||||
@ -982,30 +1072,10 @@ restore:
|
||||
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 = {
|
||||
.cd_factor = 0x3cc,
|
||||
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.info.voltage_max_design = 4351000,
|
||||
.info.voltage_min_design = 3100000,
|
||||
.info.charge_full_design = 1740000,
|
||||
.bat.constant_charge_voltage_max_uv = 4200000,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id cpcap_battery_id_table[] = {
|
||||
{
|
||||
.compatible = "motorola,cpcap-battery",
|
||||
.data = &cpcap_battery_default_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
@ -1028,19 +1098,15 @@ static int cpcap_battery_probe(struct platform_device *pdev)
|
||||
struct cpcap_battery_ddata *ddata;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
int error;
|
||||
const struct cpcap_battery_config *cfg;
|
||||
|
||||
cfg = device_get_match_data(&pdev->dev);
|
||||
if (!cfg)
|
||||
return -ENODEV;
|
||||
|
||||
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
|
||||
if (!ddata)
|
||||
return -ENOMEM;
|
||||
|
||||
cpcap_battery_detect_battery_type(ddata);
|
||||
|
||||
INIT_LIST_HEAD(&ddata->irq_list);
|
||||
ddata->dev = &pdev->dev;
|
||||
memcpy(&ddata->config, cfg, sizeof(ddata->config));
|
||||
|
||||
ddata->reg = dev_get_regmap(ddata->dev->parent, NULL);
|
||||
if (!ddata->reg)
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#define DRV_NAME "cros-ec-pchg"
|
||||
#define PCHG_DIR_PREFIX "peripheral"
|
||||
@ -237,46 +238,22 @@ static int cros_pchg_event(const struct charger_data *charger,
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static u32 cros_get_device_event(const struct charger_data *charger)
|
||||
{
|
||||
struct ec_params_device_event req;
|
||||
struct ec_response_device_event rsp;
|
||||
struct device *dev = charger->dev;
|
||||
int ret;
|
||||
|
||||
req.param = EC_DEVICE_EVENT_PARAM_GET_CURRENT_EVENTS;
|
||||
ret = cros_pchg_ec_command(charger, 0, EC_CMD_DEVICE_EVENT,
|
||||
&req, sizeof(req), &rsp, sizeof(rsp));
|
||||
if (ret < 0) {
|
||||
dev_warn(dev, "Unable to get device events (err:%d)\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rsp.event_mask;
|
||||
}
|
||||
|
||||
static int cros_ec_notify(struct notifier_block *nb,
|
||||
unsigned long queued_during_suspend,
|
||||
void *data)
|
||||
{
|
||||
struct cros_ec_device *ec_dev = (struct cros_ec_device *)data;
|
||||
u32 host_event = cros_ec_get_host_event(ec_dev);
|
||||
struct cros_ec_device *ec_dev = data;
|
||||
struct charger_data *charger =
|
||||
container_of(nb, struct charger_data, notifier);
|
||||
u32 device_event_mask;
|
||||
u32 host_event;
|
||||
|
||||
if (!host_event)
|
||||
if (ec_dev->event_data.event_type != EC_MKBP_EVENT_PCHG ||
|
||||
ec_dev->event_size != sizeof(host_event))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
if (!(host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_DEVICE)))
|
||||
return NOTIFY_DONE;
|
||||
host_event = get_unaligned_le32(&ec_dev->event_data.data.host_event);
|
||||
|
||||
/*
|
||||
* todo: Retrieve device event mask in common place
|
||||
* (e.g. cros_ec_proto.c).
|
||||
*/
|
||||
device_event_mask = cros_get_device_event(charger);
|
||||
if (!(device_event_mask & EC_DEVICE_EVENT_MASK(EC_DEVICE_EVENT_WLC)))
|
||||
if (!(host_event & EC_MKBP_PCHG_DEVICE_EVENT))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
return cros_pchg_event(charger, host_event);
|
||||
|
@ -104,7 +104,7 @@ static int cros_usbpd_charger_ec_command(struct charger_data *charger,
|
||||
struct cros_ec_command *msg;
|
||||
int ret;
|
||||
|
||||
msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
|
||||
msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <asm/div64.h>
|
||||
#include <linux/mfd/da9150/core.h>
|
||||
#include <linux/mfd/da9150/registers.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
|
||||
/* Core2Wire */
|
||||
#define DA9150_QIF_READ (0x0 << 7)
|
||||
@ -506,43 +507,30 @@ static int da9150_fg_probe(struct platform_device *pdev)
|
||||
* work for reporting data updates.
|
||||
*/
|
||||
if (fg->interval) {
|
||||
INIT_DELAYED_WORK(&fg->work, da9150_fg_work);
|
||||
ret = devm_delayed_work_autocancel(dev, &fg->work,
|
||||
da9150_fg_work);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to init work\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
schedule_delayed_work(&fg->work,
|
||||
msecs_to_jiffies(fg->interval));
|
||||
}
|
||||
|
||||
/* Register IRQ */
|
||||
irq = platform_get_irq_byname(pdev, "FG");
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "Failed to get IRQ FG: %d\n", irq);
|
||||
ret = irq;
|
||||
goto irq_fail;
|
||||
}
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq,
|
||||
IRQF_ONESHOT, "FG", fg);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret);
|
||||
goto irq_fail;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
irq_fail:
|
||||
if (fg->interval)
|
||||
cancel_delayed_work(&fg->work);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da9150_fg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct da9150_fg *fg = platform_get_drvdata(pdev);
|
||||
|
||||
if (fg->interval)
|
||||
cancel_delayed_work(&fg->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da9150_fg_resume(struct platform_device *pdev)
|
||||
@ -564,7 +552,6 @@ static struct platform_driver da9150_fg_driver = {
|
||||
.name = "da9150-fuel-gauge",
|
||||
},
|
||||
.probe = da9150_fg_probe,
|
||||
.remove = da9150_fg_remove,
|
||||
.resume = da9150_fg_resume,
|
||||
};
|
||||
|
||||
|
638
drivers/power/supply/ip5xxx_power.c
Normal file
638
drivers/power/supply/ip5xxx_power.c
Normal file
@ -0,0 +1,638 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define IP5XXX_SYS_CTL0 0x01
|
||||
#define IP5XXX_SYS_CTL0_WLED_DET_EN BIT(4)
|
||||
#define IP5XXX_SYS_CTL0_WLED_EN BIT(3)
|
||||
#define IP5XXX_SYS_CTL0_BOOST_EN BIT(2)
|
||||
#define IP5XXX_SYS_CTL0_CHARGER_EN BIT(1)
|
||||
#define IP5XXX_SYS_CTL1 0x02
|
||||
#define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN BIT(1)
|
||||
#define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN BIT(0)
|
||||
#define IP5XXX_SYS_CTL2 0x0c
|
||||
#define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH GENMASK(7, 3)
|
||||
#define IP5XXX_SYS_CTL3 0x03
|
||||
#define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL GENMASK(7, 6)
|
||||
#define IP5XXX_SYS_CTL3_BTN_SHDN_EN BIT(5)
|
||||
#define IP5XXX_SYS_CTL4 0x04
|
||||
#define IP5XXX_SYS_CTL4_SHDN_TIME_SEL GENMASK(7, 6)
|
||||
#define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN BIT(5)
|
||||
#define IP5XXX_SYS_CTL5 0x07
|
||||
#define IP5XXX_SYS_CTL5_NTC_DIS BIT(6)
|
||||
#define IP5XXX_SYS_CTL5_WLED_MODE_SEL BIT(1)
|
||||
#define IP5XXX_SYS_CTL5_BTN_SHDN_SEL BIT(0)
|
||||
#define IP5XXX_CHG_CTL1 0x22
|
||||
#define IP5XXX_CHG_CTL1_BOOST_UVP_SEL GENMASK(3, 2)
|
||||
#define IP5XXX_CHG_CTL2 0x24
|
||||
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL GENMASK(6, 5)
|
||||
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V (0x0 << 5)
|
||||
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V (0x1 << 5)
|
||||
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V (0x2 << 5)
|
||||
#define IP5XXX_CHG_CTL2_CONST_VOLT_SEL GENMASK(2, 1)
|
||||
#define IP5XXX_CHG_CTL4 0x26
|
||||
#define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN BIT(6)
|
||||
#define IP5XXX_CHG_CTL4A 0x25
|
||||
#define IP5XXX_CHG_CTL4A_CONST_CUR_SEL GENMASK(4, 0)
|
||||
#define IP5XXX_MFP_CTL0 0x51
|
||||
#define IP5XXX_MFP_CTL1 0x52
|
||||
#define IP5XXX_GPIO_CTL2 0x53
|
||||
#define IP5XXX_GPIO_CTL2A 0x54
|
||||
#define IP5XXX_GPIO_CTL3 0x55
|
||||
#define IP5XXX_READ0 0x71
|
||||
#define IP5XXX_READ0_CHG_STAT GENMASK(7, 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_IDLE (0x0 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_TRICKLE (0x1 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_CONST_VOLT (0x2 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_CONST_CUR (0x3 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP (0x4 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_FULL (0x5 << 5)
|
||||
#define IP5XXX_READ0_CHG_STAT_TIMEOUT (0x6 << 5)
|
||||
#define IP5XXX_READ0_CHG_OP BIT(4)
|
||||
#define IP5XXX_READ0_CHG_END BIT(3)
|
||||
#define IP5XXX_READ0_CONST_VOLT_TIMEOUT BIT(2)
|
||||
#define IP5XXX_READ0_CHG_TIMEOUT BIT(1)
|
||||
#define IP5XXX_READ0_TRICKLE_TIMEOUT BIT(0)
|
||||
#define IP5XXX_READ0_TIMEOUT GENMASK(2, 0)
|
||||
#define IP5XXX_READ1 0x72
|
||||
#define IP5XXX_READ1_WLED_PRESENT BIT(7)
|
||||
#define IP5XXX_READ1_LIGHT_LOAD BIT(6)
|
||||
#define IP5XXX_READ1_VIN_OVERVOLT BIT(5)
|
||||
#define IP5XXX_READ2 0x77
|
||||
#define IP5XXX_READ2_BTN_PRESS BIT(3)
|
||||
#define IP5XXX_READ2_BTN_LONG_PRESS BIT(1)
|
||||
#define IP5XXX_READ2_BTN_SHORT_PRESS BIT(0)
|
||||
#define IP5XXX_BATVADC_DAT0 0xa2
|
||||
#define IP5XXX_BATVADC_DAT1 0xa3
|
||||
#define IP5XXX_BATIADC_DAT0 0xa4
|
||||
#define IP5XXX_BATIADC_DAT1 0xa5
|
||||
#define IP5XXX_BATOCV_DAT0 0xa8
|
||||
#define IP5XXX_BATOCV_DAT1 0xa9
|
||||
|
||||
struct ip5xxx {
|
||||
struct regmap *regmap;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
/*
|
||||
* The IP5xxx charger only responds on I2C when it is "awake". The charger is
|
||||
* generally only awake when VIN is powered or when its boost converter is
|
||||
* enabled. Going into shutdown resets all register values. To handle this:
|
||||
* 1) When any bus error occurs, assume the charger has gone into shutdown.
|
||||
* 2) Attempt the initialization sequence on each subsequent register access
|
||||
* until it succeeds.
|
||||
*/
|
||||
static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(ip5xxx->regmap, reg, val);
|
||||
if (ret)
|
||||
ip5xxx->initialized = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg,
|
||||
unsigned int mask, unsigned int val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val);
|
||||
if (ret)
|
||||
ip5xxx->initialized = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ip5xxx_initialize(struct power_supply *psy)
|
||||
{
|
||||
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||||
int ret;
|
||||
|
||||
if (ip5xxx->initialized)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Disable shutdown under light load.
|
||||
* Enable power on when under load.
|
||||
*/
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1,
|
||||
IP5XXX_SYS_CTL1_LIGHT_SHDN_EN |
|
||||
IP5XXX_SYS_CTL1_LOAD_PWRUP_EN,
|
||||
IP5XXX_SYS_CTL1_LOAD_PWRUP_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Enable shutdown after a long button press (as configured below).
|
||||
*/
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3,
|
||||
IP5XXX_SYS_CTL3_BTN_SHDN_EN,
|
||||
IP5XXX_SYS_CTL3_BTN_SHDN_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Power on automatically when VIN is removed.
|
||||
*/
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4,
|
||||
IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN,
|
||||
IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Enable the NTC.
|
||||
* Configure the button for two presses => LED, long press => shutdown.
|
||||
*/
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5,
|
||||
IP5XXX_SYS_CTL5_NTC_DIS |
|
||||
IP5XXX_SYS_CTL5_WLED_MODE_SEL |
|
||||
IP5XXX_SYS_CTL5_BTN_SHDN_SEL,
|
||||
IP5XXX_SYS_CTL5_WLED_MODE_SEL |
|
||||
IP5XXX_SYS_CTL5_BTN_SHDN_SEL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ip5xxx->initialized = true;
|
||||
dev_dbg(psy->dev.parent, "Initialized after power on\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum power_supply_property ip5xxx_battery_properties[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_OCV,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
};
|
||||
|
||||
static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val)
|
||||
{
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (rval & IP5XXX_READ0_CHG_STAT) {
|
||||
case IP5XXX_READ0_CHG_STAT_IDLE:
|
||||
*val = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
break;
|
||||
case IP5XXX_READ0_CHG_STAT_TRICKLE:
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_CUR:
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
|
||||
*val = POWER_SUPPLY_STATUS_CHARGING;
|
||||
break;
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
|
||||
case IP5XXX_READ0_CHG_STAT_FULL:
|
||||
*val = POWER_SUPPLY_STATUS_FULL;
|
||||
break;
|
||||
case IP5XXX_READ0_CHG_STAT_TIMEOUT:
|
||||
*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val)
|
||||
{
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (rval & IP5XXX_READ0_CHG_STAT) {
|
||||
case IP5XXX_READ0_CHG_STAT_IDLE:
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
|
||||
case IP5XXX_READ0_CHG_STAT_FULL:
|
||||
case IP5XXX_READ0_CHG_STAT_TIMEOUT:
|
||||
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||||
break;
|
||||
case IP5XXX_READ0_CHG_STAT_TRICKLE:
|
||||
*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
||||
break;
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_CUR:
|
||||
case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
|
||||
*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val)
|
||||
{
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (rval & IP5XXX_READ0_TIMEOUT)
|
||||
*val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
||||
else
|
||||
*val = POWER_SUPPLY_HEALTH_GOOD;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val)
|
||||
{
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* It is not clear what this will return if
|
||||
* IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set...
|
||||
*/
|
||||
switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) {
|
||||
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V:
|
||||
*val = 4200000;
|
||||
break;
|
||||
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V:
|
||||
*val = 4300000;
|
||||
break;
|
||||
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V:
|
||||
*val = 4350000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx,
|
||||
u8 lo_reg, u8 hi_reg, int *val)
|
||||
{
|
||||
unsigned int hi, lo;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, lo_reg, &lo);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, hi_reg, &hi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(hi << 8 | lo, 13);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||||
int raw, ret, vmax;
|
||||
unsigned int rval;
|
||||
|
||||
ret = ip5xxx_initialize(psy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
return ip5xxx_battery_get_status(ip5xxx, &val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||||
return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
return ip5xxx_battery_get_health(ip5xxx, &val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0,
|
||||
IP5XXX_BATVADC_DAT1, &raw);
|
||||
|
||||
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
|
||||
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0,
|
||||
IP5XXX_BATOCV_DAT1, &raw);
|
||||
|
||||
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0,
|
||||
IP5XXX_BATIADC_DAT1, &raw);
|
||||
|
||||
val->intval = DIV_ROUND_CLOSEST(raw * 745985, 1000);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL;
|
||||
val->intval = 100000 * rval;
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = 100000 * 0x1f;
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL;
|
||||
val->intval = vmax + 14000 * (rval >> 1);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = vmax + 14000 * 3;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val)
|
||||
{
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
switch (val) {
|
||||
case 4200000:
|
||||
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V;
|
||||
break;
|
||||
case 4300000:
|
||||
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V;
|
||||
break;
|
||||
case 4350000:
|
||||
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
|
||||
IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4,
|
||||
IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN,
|
||||
IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||||
unsigned int rval;
|
||||
int ret, vmax;
|
||||
|
||||
ret = ip5xxx_initialize(psy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
switch (val->intval) {
|
||||
case POWER_SUPPLY_STATUS_CHARGING:
|
||||
rval = IP5XXX_SYS_CTL0_CHARGER_EN;
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_DISCHARGING:
|
||||
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
||||
rval = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
|
||||
IP5XXX_SYS_CTL0_CHARGER_EN, rval);
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
rval = val->intval / 100000;
|
||||
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A,
|
||||
IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval);
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rval = ((val->intval - vmax) / 14000) << 1;
|
||||
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
|
||||
IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ip5xxx_battery_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
return psp == POWER_SUPPLY_PROP_STATUS ||
|
||||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
|
||||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
|
||||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
|
||||
}
|
||||
|
||||
static const struct power_supply_desc ip5xxx_battery_desc = {
|
||||
.name = "ip5xxx-battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.properties = ip5xxx_battery_properties,
|
||||
.num_properties = ARRAY_SIZE(ip5xxx_battery_properties),
|
||||
.get_property = ip5xxx_battery_get_property,
|
||||
.set_property = ip5xxx_battery_set_property,
|
||||
.property_is_writeable = ip5xxx_battery_property_is_writeable,
|
||||
};
|
||||
|
||||
static const enum power_supply_property ip5xxx_boost_properties[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
||||
};
|
||||
|
||||
static int ip5xxx_boost_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_initialize(psy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||||
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL;
|
||||
val->intval = 4530000 + 100000 * (rval >> 2);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ip5xxx_boost_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = ip5xxx_initialize(psy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0;
|
||||
return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
|
||||
IP5XXX_SYS_CTL0_BOOST_EN, rval);
|
||||
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||||
rval = ((val->intval - 4530000) / 100000) << 2;
|
||||
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1,
|
||||
IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ip5xxx_boost_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct power_supply_desc ip5xxx_boost_desc = {
|
||||
.name = "ip5xxx-boost",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.properties = ip5xxx_boost_properties,
|
||||
.num_properties = ARRAY_SIZE(ip5xxx_boost_properties),
|
||||
.get_property = ip5xxx_boost_get_property,
|
||||
.set_property = ip5xxx_boost_set_property,
|
||||
.property_is_writeable = ip5xxx_boost_property_is_writeable,
|
||||
};
|
||||
|
||||
static const struct regmap_config ip5xxx_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = IP5XXX_BATOCV_DAT1,
|
||||
};
|
||||
|
||||
static int ip5xxx_power_probe(struct i2c_client *client)
|
||||
{
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct device *dev = &client->dev;
|
||||
struct power_supply *psy;
|
||||
struct ip5xxx *ip5xxx;
|
||||
|
||||
ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
|
||||
if (!ip5xxx)
|
||||
return -ENOMEM;
|
||||
|
||||
ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config);
|
||||
if (IS_ERR(ip5xxx->regmap))
|
||||
return PTR_ERR(ip5xxx->regmap);
|
||||
|
||||
psy_cfg.of_node = dev->of_node;
|
||||
psy_cfg.drv_data = ip5xxx;
|
||||
|
||||
psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg);
|
||||
if (IS_ERR(psy))
|
||||
return PTR_ERR(psy);
|
||||
|
||||
psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg);
|
||||
if (IS_ERR(psy))
|
||||
return PTR_ERR(psy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ip5xxx_power_of_match[] = {
|
||||
{ .compatible = "injoinic,ip5108" },
|
||||
{ .compatible = "injoinic,ip5109" },
|
||||
{ .compatible = "injoinic,ip5207" },
|
||||
{ .compatible = "injoinic,ip5209" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match);
|
||||
|
||||
static struct i2c_driver ip5xxx_power_driver = {
|
||||
.probe_new = ip5xxx_power_probe,
|
||||
.driver = {
|
||||
.name = "ip5xxx-power",
|
||||
.of_match_table = ip5xxx_power_of_match,
|
||||
}
|
||||
};
|
||||
module_i2c_driver(ip5xxx_power_driver);
|
||||
|
||||
MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
|
||||
MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -112,7 +112,8 @@ static int ltc294x_read_regs(struct i2c_client *client,
|
||||
|
||||
ret = i2c_transfer(client->adapter, &msgs[0], 2);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "ltc2941 read_reg failed!\n");
|
||||
dev_err(&client->dev, "ltc2941 read_reg(0x%x[%d]) failed: %pe\n",
|
||||
reg, num_regs, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -130,7 +131,8 @@ static int ltc294x_write_regs(struct i2c_client *client,
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "ltc2941 write_reg failed!\n");
|
||||
dev_err(&client->dev, "ltc2941 write_reg(0x%x[%d]) failed: %pe\n",
|
||||
reg, num_regs, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -148,11 +150,8 @@ static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp)
|
||||
|
||||
/* Read status and control registers */
|
||||
ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&info->client->dev,
|
||||
"Could not read registers from device\n");
|
||||
goto error_exit;
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) |
|
||||
LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED;
|
||||
@ -172,17 +171,11 @@ static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp)
|
||||
if (value != control) {
|
||||
ret = ltc294x_write_regs(info->client,
|
||||
LTC294X_REG_CONTROL, &control, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&info->client->dev,
|
||||
"Could not write register\n");
|
||||
goto error_exit;
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc294x_read_charge_register(const struct ltc294x_info *info,
|
||||
@ -472,11 +465,9 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
||||
/* r_sense can be negative, when sense+ is connected to the battery
|
||||
* instead of the sense-. This results in reversed measurements. */
|
||||
ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Could not find lltc,resistor-sense in devicetree\n");
|
||||
return ret;
|
||||
}
|
||||
info->r_sense = r_sense;
|
||||
|
||||
ret = of_property_read_u32(np, "lltc,prescaler-exponent",
|
||||
@ -490,23 +481,21 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
||||
if (info->id == LTC2943_ID) {
|
||||
if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP)
|
||||
prescaler_exp = LTC2943_MAX_PRESCALER_EXP;
|
||||
info->Qlsb = ((340 * 50000) / r_sense) /
|
||||
(4096 / (1 << (2*prescaler_exp)));
|
||||
info->Qlsb = ((340 * 50000) / r_sense) >>
|
||||
(12 - 2*prescaler_exp);
|
||||
} else {
|
||||
if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP)
|
||||
prescaler_exp = LTC2941_MAX_PRESCALER_EXP;
|
||||
info->Qlsb = ((85 * 50000) / r_sense) /
|
||||
(128 / (1 << prescaler_exp));
|
||||
info->Qlsb = ((85 * 50000) / r_sense) >>
|
||||
(7 - prescaler_exp);
|
||||
}
|
||||
|
||||
/* Read status register to check for LTC2942 */
|
||||
if (info->id == LTC2941_ID || info->id == LTC2942_ID) {
|
||||
ret = ltc294x_read_regs(client, LTC294X_REG_STATUS, &status, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Could not read status register\n");
|
||||
return ret;
|
||||
}
|
||||
if (status & LTC2941_REG_STATUS_CHIP_ID)
|
||||
info->id = LTC2941_ID;
|
||||
else
|
||||
@ -545,19 +534,17 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
||||
return ret;
|
||||
|
||||
ret = ltc294x_reset(info, prescaler_exp);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Communication with chip failed\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Communication with chip failed\n");
|
||||
|
||||
info->supply = devm_power_supply_register(&client->dev,
|
||||
&info->supply_desc, &psy_cfg);
|
||||
if (IS_ERR(info->supply)) {
|
||||
dev_err(&client->dev, "failed to register ltc2941\n");
|
||||
return PTR_ERR(info->supply);
|
||||
} else {
|
||||
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
|
||||
}
|
||||
if (IS_ERR(info->supply))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(info->supply),
|
||||
"failed to register ltc2941\n");
|
||||
|
||||
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
|
||||
#define MAX14656_MANUFACTURER "Maxim Integrated"
|
||||
#define MAX14656_NAME "max14656"
|
||||
@ -233,14 +234,6 @@ static enum power_supply_property max14656_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static void stop_irq_work(void *data)
|
||||
{
|
||||
struct max14656_chip *chip = data;
|
||||
|
||||
cancel_delayed_work_sync(&chip->irq_work);
|
||||
}
|
||||
|
||||
|
||||
static int max14656_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
@ -286,10 +279,10 @@ static int max14656_probe(struct i2c_client *client,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&chip->irq_work, max14656_irq_worker);
|
||||
ret = devm_add_action(dev, stop_irq_work, chip);
|
||||
ret = devm_delayed_work_autocancel(dev, &chip->irq_work,
|
||||
max14656_irq_worker);
|
||||
if (ret) {
|
||||
dev_err(dev, "devm_add_action %d failed\n", ret);
|
||||
dev_err(dev, "devm_delayed_work_autocancel %d failed\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
// This driver is based on max17040_battery.c
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
@ -1030,13 +1031,6 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
|
||||
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
|
||||
};
|
||||
|
||||
static void max17042_stop_work(void *data)
|
||||
{
|
||||
struct max17042_chip *chip = data;
|
||||
|
||||
cancel_work_sync(&chip->work);
|
||||
}
|
||||
|
||||
static int max17042_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
@ -1142,8 +1136,8 @@ static int max17042_probe(struct i2c_client *client,
|
||||
|
||||
regmap_read(chip->regmap, MAX17042_STATUS, &val);
|
||||
if (val & STATUS_POR_BIT) {
|
||||
INIT_WORK(&chip->work, max17042_init_worker);
|
||||
ret = devm_add_action(&client->dev, max17042_stop_work, chip);
|
||||
ret = devm_work_autocancel(&client->dev, &chip->work,
|
||||
max17042_init_worker);
|
||||
if (ret)
|
||||
return ret;
|
||||
schedule_work(&chip->work);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/mfd/max8997.h>
|
||||
#include <linux/mfd/max8997-private.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
|
||||
/* MAX8997_REG_STATUS4 */
|
||||
#define DCINOK_SHIFT 1
|
||||
@ -94,13 +95,6 @@ static int max8997_battery_get_property(struct power_supply *psy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max8997_battery_extcon_evt_stop_work(void *data)
|
||||
{
|
||||
struct charger_data *charger = data;
|
||||
|
||||
cancel_work_sync(&charger->extcon_work);
|
||||
}
|
||||
|
||||
static void max8997_battery_extcon_evt_worker(struct work_struct *work)
|
||||
{
|
||||
struct charger_data *charger =
|
||||
@ -255,8 +249,8 @@ static int max8997_battery_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
if (!IS_ERR(charger->reg) && !IS_ERR_OR_NULL(charger->edev)) {
|
||||
INIT_WORK(&charger->extcon_work, max8997_battery_extcon_evt_worker);
|
||||
ret = devm_add_action(&pdev->dev, max8997_battery_extcon_evt_stop_work, charger);
|
||||
ret = devm_work_autocancel(&pdev->dev, &charger->extcon_work,
|
||||
max8997_battery_extcon_evt_worker);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add extcon evt stop action: %d\n", ret);
|
||||
return ret;
|
||||
|
@ -580,11 +580,9 @@ static int mp2629_charger_probe(struct platform_device *pdev)
|
||||
charger->dev = dev;
|
||||
platform_set_drvdata(pdev, charger);
|
||||
|
||||
irq = platform_get_irq_optional(to_platform_device(dev->parent), 0);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "get irq fail: %d\n", irq);
|
||||
irq = platform_get_irq(to_platform_device(dev->parent), 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
}
|
||||
|
||||
for (i = 0; i < MP2629_MAX_FIELD; i++) {
|
||||
charger->regmap_fields[i] = devm_regmap_field_alloc(dev,
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/fixp-arith.h>
|
||||
#include "power_supply.h"
|
||||
#include "samsung-sdi-battery.h"
|
||||
|
||||
/* exported for the APM Power driver, APM emulation */
|
||||
struct class *power_supply_class;
|
||||
@ -283,8 +284,7 @@ static int power_supply_check_supplies(struct power_supply *psy)
|
||||
if (!psy->dev.parent)
|
||||
return 0;
|
||||
|
||||
nval = device_property_read_string_array(psy->dev.parent,
|
||||
"supplied-from", NULL, 0);
|
||||
nval = device_property_string_array_count(psy->dev.parent, "supplied-from");
|
||||
if (nval <= 0)
|
||||
return 0;
|
||||
|
||||
@ -376,46 +376,49 @@ int power_supply_is_system_supplied(void)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_is_system_supplied);
|
||||
|
||||
static int __power_supply_get_supplier_max_current(struct device *dev,
|
||||
void *data)
|
||||
struct psy_get_supplier_prop_data {
|
||||
struct power_supply *psy;
|
||||
enum power_supply_property psp;
|
||||
union power_supply_propval *val;
|
||||
};
|
||||
|
||||
static int __power_supply_get_supplier_property(struct device *dev, void *_data)
|
||||
{
|
||||
union power_supply_propval ret = {0,};
|
||||
struct power_supply *epsy = dev_get_drvdata(dev);
|
||||
struct power_supply *psy = data;
|
||||
struct psy_get_supplier_prop_data *data = _data;
|
||||
|
||||
if (__power_supply_is_supplied_by(epsy, psy))
|
||||
if (!epsy->desc->get_property(epsy,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
&ret))
|
||||
return ret.intval;
|
||||
if (__power_supply_is_supplied_by(epsy, data->psy))
|
||||
if (!epsy->desc->get_property(epsy, data->psp, data->val))
|
||||
return 1; /* Success */
|
||||
|
||||
return 0;
|
||||
return 0; /* Continue iterating */
|
||||
}
|
||||
|
||||
int power_supply_set_input_current_limit_from_supplier(struct power_supply *psy)
|
||||
int power_supply_get_property_from_supplier(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
union power_supply_propval val = {0,};
|
||||
int curr;
|
||||
|
||||
if (!psy->desc->set_property)
|
||||
return -EINVAL;
|
||||
struct psy_get_supplier_prop_data data = {
|
||||
.psy = psy,
|
||||
.psp = psp,
|
||||
.val = val,
|
||||
};
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This function is not intended for use with a supply with multiple
|
||||
* suppliers, we simply pick the first supply to report a non 0
|
||||
* max-current.
|
||||
* suppliers, we simply pick the first supply to report the psp.
|
||||
*/
|
||||
curr = class_for_each_device(power_supply_class, NULL, psy,
|
||||
__power_supply_get_supplier_max_current);
|
||||
if (curr <= 0)
|
||||
return (curr == 0) ? -ENODEV : curr;
|
||||
ret = class_for_each_device(power_supply_class, NULL, &data,
|
||||
__power_supply_get_supplier_property);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret == 0)
|
||||
return -ENODEV;
|
||||
|
||||
val.intval = curr;
|
||||
|
||||
return psy->desc->set_property(psy,
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_set_input_current_limit_from_supplier);
|
||||
EXPORT_SYMBOL_GPL(power_supply_get_property_from_supplier);
|
||||
|
||||
int power_supply_set_battery_charged(struct power_supply *psy)
|
||||
{
|
||||
@ -568,14 +571,50 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
||||
{
|
||||
struct power_supply_resistance_temp_table *resist_table;
|
||||
struct power_supply_battery_info *info;
|
||||
struct device_node *battery_np;
|
||||
struct device_node *battery_np = NULL;
|
||||
struct fwnode_reference_args args;
|
||||
struct fwnode_handle *fwnode;
|
||||
const char *value;
|
||||
int err, len, index;
|
||||
const __be32 *list;
|
||||
u32 min_max[2];
|
||||
|
||||
if (psy->of_node) {
|
||||
battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
|
||||
if (!battery_np)
|
||||
return -ENODEV;
|
||||
|
||||
fwnode = fwnode_handle_get(of_fwnode_handle(battery_np));
|
||||
} else {
|
||||
err = fwnode_property_get_reference_args(
|
||||
dev_fwnode(psy->dev.parent),
|
||||
"monitored-battery", NULL, 0, 0, &args);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
fwnode = args.fwnode;
|
||||
}
|
||||
|
||||
err = fwnode_property_read_string(fwnode, "compatible", &value);
|
||||
if (err)
|
||||
goto out_put_node;
|
||||
|
||||
|
||||
/* Try static batteries first */
|
||||
err = samsung_sdi_battery_get_info(&psy->dev, value, &info);
|
||||
if (!err)
|
||||
goto out_ret_pointer;
|
||||
|
||||
if (strcmp("simple-battery", value)) {
|
||||
err = -ENODEV;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
info = devm_kmalloc(&psy->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
if (!info) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
|
||||
info->energy_full_design_uwh = -EINVAL;
|
||||
@ -590,6 +629,11 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
||||
info->precharge_voltage_max_uv = -EINVAL;
|
||||
info->charge_restart_voltage_uv = -EINVAL;
|
||||
info->overvoltage_limit_uv = -EINVAL;
|
||||
info->maintenance_charge = NULL;
|
||||
info->alert_low_temp_charge_current_ua = -EINVAL;
|
||||
info->alert_low_temp_charge_voltage_uv = -EINVAL;
|
||||
info->alert_high_temp_charge_current_ua = -EINVAL;
|
||||
info->alert_high_temp_charge_voltage_uv = -EINVAL;
|
||||
info->temp_ambient_alert_min = INT_MIN;
|
||||
info->temp_ambient_alert_max = INT_MAX;
|
||||
info->temp_alert_min = INT_MIN;
|
||||
@ -597,7 +641,9 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
||||
info->temp_min = INT_MIN;
|
||||
info->temp_max = INT_MAX;
|
||||
info->factory_internal_resistance_uohm = -EINVAL;
|
||||
info->resist_table = NULL;
|
||||
info->resist_table = NULL;
|
||||
info->bti_resistance_ohm = -EINVAL;
|
||||
info->bti_resistance_tolerance = -EINVAL;
|
||||
|
||||
for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
|
||||
info->ocv_table[index] = NULL;
|
||||
@ -605,31 +651,12 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
||||
info->ocv_table_size[index] = -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)
|
||||
goto out_put_node;
|
||||
|
||||
if (strcmp("simple-battery", value)) {
|
||||
err = -ENODEV;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
/* The property and field names below must correspond to elements
|
||||
* in enum power_supply_property. For reasoning, see
|
||||
* Documentation/power/power_supply_class.rst.
|
||||
*/
|
||||
|
||||
if (!of_property_read_string(battery_np, "device-chemistry", &value)) {
|
||||
if (!fwnode_property_read_string(fwnode, "device-chemistry", &value)) {
|
||||
if (!strcmp("nickel-cadmium", value))
|
||||
info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
|
||||
else if (!strcmp("nickel-metal-hydride", value))
|
||||
@ -647,45 +674,56 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
||||
dev_warn(&psy->dev, "%s unknown battery type\n", value);
|
||||
}
|
||||
|
||||
of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
|
||||
fwnode_property_read_u32(fwnode, "energy-full-design-microwatt-hours",
|
||||
&info->energy_full_design_uwh);
|
||||
of_property_read_u32(battery_np, "charge-full-design-microamp-hours",
|
||||
fwnode_property_read_u32(fwnode, "charge-full-design-microamp-hours",
|
||||
&info->charge_full_design_uah);
|
||||
of_property_read_u32(battery_np, "voltage-min-design-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "voltage-min-design-microvolt",
|
||||
&info->voltage_min_design_uv);
|
||||
of_property_read_u32(battery_np, "voltage-max-design-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "voltage-max-design-microvolt",
|
||||
&info->voltage_max_design_uv);
|
||||
of_property_read_u32(battery_np, "trickle-charge-current-microamp",
|
||||
fwnode_property_read_u32(fwnode, "trickle-charge-current-microamp",
|
||||
&info->tricklecharge_current_ua);
|
||||
of_property_read_u32(battery_np, "precharge-current-microamp",
|
||||
fwnode_property_read_u32(fwnode, "precharge-current-microamp",
|
||||
&info->precharge_current_ua);
|
||||
of_property_read_u32(battery_np, "precharge-upper-limit-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "precharge-upper-limit-microvolt",
|
||||
&info->precharge_voltage_max_uv);
|
||||
of_property_read_u32(battery_np, "charge-term-current-microamp",
|
||||
fwnode_property_read_u32(fwnode, "charge-term-current-microamp",
|
||||
&info->charge_term_current_ua);
|
||||
of_property_read_u32(battery_np, "re-charge-voltage-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "re-charge-voltage-microvolt",
|
||||
&info->charge_restart_voltage_uv);
|
||||
of_property_read_u32(battery_np, "over-voltage-threshold-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "over-voltage-threshold-microvolt",
|
||||
&info->overvoltage_limit_uv);
|
||||
of_property_read_u32(battery_np, "constant-charge-current-max-microamp",
|
||||
fwnode_property_read_u32(fwnode, "constant-charge-current-max-microamp",
|
||||
&info->constant_charge_current_max_ua);
|
||||
of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt",
|
||||
fwnode_property_read_u32(fwnode, "constant-charge-voltage-max-microvolt",
|
||||
&info->constant_charge_voltage_max_uv);
|
||||
of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
|
||||
fwnode_property_read_u32(fwnode, "factory-internal-resistance-micro-ohms",
|
||||
&info->factory_internal_resistance_uohm);
|
||||
|
||||
of_property_read_u32_index(battery_np, "ambient-celsius",
|
||||
0, &info->temp_ambient_alert_min);
|
||||
of_property_read_u32_index(battery_np, "ambient-celsius",
|
||||
1, &info->temp_ambient_alert_max);
|
||||
of_property_read_u32_index(battery_np, "alert-celsius",
|
||||
0, &info->temp_alert_min);
|
||||
of_property_read_u32_index(battery_np, "alert-celsius",
|
||||
1, &info->temp_alert_max);
|
||||
of_property_read_u32_index(battery_np, "operating-range-celsius",
|
||||
0, &info->temp_min);
|
||||
of_property_read_u32_index(battery_np, "operating-range-celsius",
|
||||
1, &info->temp_max);
|
||||
if (!fwnode_property_read_u32_array(fwnode, "ambient-celsius",
|
||||
min_max, ARRAY_SIZE(min_max))) {
|
||||
info->temp_ambient_alert_min = min_max[0];
|
||||
info->temp_ambient_alert_max = min_max[1];
|
||||
}
|
||||
if (!fwnode_property_read_u32_array(fwnode, "alert-celsius",
|
||||
min_max, ARRAY_SIZE(min_max))) {
|
||||
info->temp_alert_min = min_max[0];
|
||||
info->temp_alert_max = min_max[1];
|
||||
}
|
||||
if (!fwnode_property_read_u32_array(fwnode, "operating-range-celsius",
|
||||
min_max, ARRAY_SIZE(min_max))) {
|
||||
info->temp_min = min_max[0];
|
||||
info->temp_max = min_max[1];
|
||||
}
|
||||
|
||||
/*
|
||||
* The below code uses raw of-data parsing to parse
|
||||
* /schemas/types.yaml#/definitions/uint32-matrix
|
||||
* data, so for now this is only support with of.
|
||||
*/
|
||||
if (!battery_np)
|
||||
goto out_ret_pointer;
|
||||
|
||||
len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
|
||||
if (len < 0 && len != -EINVAL) {
|
||||
@ -760,6 +798,7 @@ out_ret_pointer:
|
||||
*info_out = info;
|
||||
|
||||
out_put_node:
|
||||
fwnode_handle_put(fwnode);
|
||||
of_node_put(battery_np);
|
||||
return err;
|
||||
}
|
||||
@ -784,7 +823,7 @@ EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
|
||||
|
||||
/**
|
||||
* power_supply_temp2resist_simple() - find the battery internal resistance
|
||||
* percent
|
||||
* percent from temperature
|
||||
* @table: Pointer to battery resistance temperature table
|
||||
* @table_len: The table length
|
||||
* @temp: Current temperature
|
||||
@ -821,6 +860,81 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
|
||||
|
||||
/**
|
||||
* power_supply_vbat2ri() - find the battery internal resistance
|
||||
* from the battery voltage
|
||||
* @info: The battery information container
|
||||
* @table: Pointer to battery resistance temperature table
|
||||
* @vbat_uv: The battery voltage in microvolt
|
||||
* @charging: If we are charging (true) or not (false)
|
||||
*
|
||||
* This helper function is used to look up battery internal resistance
|
||||
* according to current battery voltage. Depending on whether the battery
|
||||
* is currently charging or not, different resistance will be returned.
|
||||
*
|
||||
* Returns the internal resistance in microohm or negative error code.
|
||||
*/
|
||||
int power_supply_vbat2ri(struct power_supply_battery_info *info,
|
||||
int vbat_uv, bool charging)
|
||||
{
|
||||
struct power_supply_vbat_ri_table *vbat2ri;
|
||||
int table_len;
|
||||
int i, high, low;
|
||||
|
||||
/*
|
||||
* If we are charging, and the battery supplies a separate table
|
||||
* for this state, we use that in order to compensate for the
|
||||
* charging voltage. Otherwise we use the main table.
|
||||
*/
|
||||
if (charging && info->vbat2ri_charging) {
|
||||
vbat2ri = info->vbat2ri_charging;
|
||||
table_len = info->vbat2ri_charging_size;
|
||||
} else {
|
||||
vbat2ri = info->vbat2ri_discharging;
|
||||
table_len = info->vbat2ri_discharging_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* If no tables are specified, or if we are above the highest voltage in
|
||||
* the voltage table, just return the factory specified internal resistance.
|
||||
*/
|
||||
if (!vbat2ri || (table_len <= 0) || (vbat_uv > vbat2ri[0].vbat_uv)) {
|
||||
if (charging && (info->factory_internal_resistance_charging_uohm > 0))
|
||||
return info->factory_internal_resistance_charging_uohm;
|
||||
else
|
||||
return info->factory_internal_resistance_uohm;
|
||||
}
|
||||
|
||||
/* Break loop at table_len - 1 because that is the highest index */
|
||||
for (i = 0; i < table_len - 1; i++)
|
||||
if (vbat_uv > vbat2ri[i].vbat_uv)
|
||||
break;
|
||||
|
||||
/* The library function will deal with high == low */
|
||||
if ((i == 0) || (i == (table_len - 1)))
|
||||
high = i;
|
||||
else
|
||||
high = i - 1;
|
||||
low = i;
|
||||
|
||||
return fixp_linear_interpolate(vbat2ri[low].vbat_uv,
|
||||
vbat2ri[low].ri_uohm,
|
||||
vbat2ri[high].vbat_uv,
|
||||
vbat2ri[high].ri_uohm,
|
||||
vbat_uv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_vbat2ri);
|
||||
|
||||
struct power_supply_maintenance_charge_table *
|
||||
power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info,
|
||||
int index)
|
||||
{
|
||||
if (index >= info->maintenance_charge_size)
|
||||
return NULL;
|
||||
return &info->maintenance_charge[index];
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_get_maintenance_charging_setting);
|
||||
|
||||
/**
|
||||
* power_supply_ocv2cap_simple() - find the battery capacity
|
||||
* @table: Pointer to battery OCV lookup table
|
||||
@ -900,6 +1014,28 @@ int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2cap);
|
||||
|
||||
bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info,
|
||||
int resistance)
|
||||
{
|
||||
int low, high;
|
||||
|
||||
/* Nothing like this can be checked */
|
||||
if (info->bti_resistance_ohm <= 0)
|
||||
return false;
|
||||
|
||||
/* This will be extremely strict and unlikely to work */
|
||||
if (info->bti_resistance_tolerance <= 0)
|
||||
return (info->bti_resistance_ohm == resistance);
|
||||
|
||||
low = info->bti_resistance_ohm -
|
||||
(info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100;
|
||||
high = info->bti_resistance_ohm +
|
||||
(info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100;
|
||||
|
||||
return ((resistance >= low) && (resistance <= high));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_battery_bti_in_range);
|
||||
|
||||
int power_supply_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
|
@ -324,11 +324,6 @@ static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
|
||||
.info = power_supply_hwmon_info,
|
||||
};
|
||||
|
||||
static void power_supply_hwmon_bitmap_free(void *data)
|
||||
{
|
||||
bitmap_free(data);
|
||||
}
|
||||
|
||||
int power_supply_add_hwmon_sysfs(struct power_supply *psy)
|
||||
{
|
||||
const struct power_supply_desc *desc = psy->desc;
|
||||
@ -349,18 +344,14 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy)
|
||||
}
|
||||
|
||||
psyhw->psy = psy;
|
||||
psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
|
||||
GFP_KERNEL);
|
||||
psyhw->props = devm_bitmap_zalloc(dev,
|
||||
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
|
||||
GFP_KERNEL);
|
||||
if (!psyhw->props) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free,
|
||||
psyhw->props);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < desc->num_properties; i++) {
|
||||
const enum power_supply_property prop = desc->properties[i];
|
||||
|
||||
|
@ -89,6 +89,7 @@ static const char * const POWER_SUPPLY_CHARGE_TYPE_TEXT[] = {
|
||||
[POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE] = "Adaptive",
|
||||
[POWER_SUPPLY_CHARGE_TYPE_CUSTOM] = "Custom",
|
||||
[POWER_SUPPLY_CHARGE_TYPE_LONGLIFE] = "Long Life",
|
||||
[POWER_SUPPLY_CHARGE_TYPE_BYPASS] = "Bypass",
|
||||
};
|
||||
|
||||
static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
|
||||
|
@ -1716,7 +1716,7 @@ static int rt9455_remove(struct i2c_client *client)
|
||||
cancel_delayed_work_sync(&info->max_charging_time_work);
|
||||
cancel_delayed_work_sync(&info->batt_presence_work);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id rt9455_i2c_id_table[] = {
|
||||
|
918
drivers/power/supply/samsung-sdi-battery.c
Normal file
918
drivers/power/supply/samsung-sdi-battery.c
Normal file
@ -0,0 +1,918 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Battery data and characteristics for Samsung SDI (Samsung Digital Interface)
|
||||
* batteries. The data is retrieved automatically into drivers using
|
||||
* the power_supply_get_battery_info() call.
|
||||
*
|
||||
* The BTI (battery type indicator) resistance in the code drops was very
|
||||
* unreliable. The resistance listed here was obtained by simply measuring
|
||||
* the BTI resistance with a multimeter on the battery.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include "samsung-sdi-battery.h"
|
||||
|
||||
struct samsung_sdi_battery {
|
||||
char *compatible;
|
||||
char *name;
|
||||
struct power_supply_battery_info info;
|
||||
};
|
||||
|
||||
/*
|
||||
* Voltage to internal resistance tables. The internal resistance varies
|
||||
* depending on the VBAT voltage, so look this up from a table. Different
|
||||
* tables apply depending on whether we are charging or not.
|
||||
*/
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = {
|
||||
{ .vbat_uv = 4240000, .ri_uohm = 160000 },
|
||||
{ .vbat_uv = 4210000, .ri_uohm = 179000 },
|
||||
{ .vbat_uv = 4180000, .ri_uohm = 183000 },
|
||||
{ .vbat_uv = 4160000, .ri_uohm = 184000 },
|
||||
{ .vbat_uv = 4140000, .ri_uohm = 191000 },
|
||||
{ .vbat_uv = 4120000, .ri_uohm = 204000 },
|
||||
{ .vbat_uv = 4076000, .ri_uohm = 220000 },
|
||||
{ .vbat_uv = 4030000, .ri_uohm = 227000 },
|
||||
{ .vbat_uv = 3986000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3916000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3842000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3773000, .ri_uohm = 287000 },
|
||||
{ .vbat_uv = 3742000, .ri_uohm = 283000 },
|
||||
{ .vbat_uv = 3709000, .ri_uohm = 277000 },
|
||||
{ .vbat_uv = 3685000, .ri_uohm = 297000 },
|
||||
{ .vbat_uv = 3646000, .ri_uohm = 310000 },
|
||||
{ .vbat_uv = 3616000, .ri_uohm = 331000 },
|
||||
{ .vbat_uv = 3602000, .ri_uohm = 370000 },
|
||||
{ .vbat_uv = 3578000, .ri_uohm = 350000 },
|
||||
{ .vbat_uv = 3553000, .ri_uohm = 321000 },
|
||||
{ .vbat_uv = 3503000, .ri_uohm = 322000 },
|
||||
{ .vbat_uv = 3400000, .ri_uohm = 269000 },
|
||||
{ .vbat_uv = 3360000, .ri_uohm = 328000 },
|
||||
{ .vbat_uv = 3330000, .ri_uohm = 305000 },
|
||||
{ .vbat_uv = 3300000, .ri_uohm = 339000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = {
|
||||
{ .vbat_uv = 4302000, .ri_uohm = 230000 },
|
||||
{ .vbat_uv = 4276000, .ri_uohm = 345000 },
|
||||
{ .vbat_uv = 4227000, .ri_uohm = 345000 },
|
||||
{ .vbat_uv = 4171000, .ri_uohm = 346000 },
|
||||
{ .vbat_uv = 4134000, .ri_uohm = 311000 },
|
||||
{ .vbat_uv = 4084000, .ri_uohm = 299000 },
|
||||
{ .vbat_uv = 4052000, .ri_uohm = 316000 },
|
||||
{ .vbat_uv = 4012000, .ri_uohm = 309000 },
|
||||
{ .vbat_uv = 3961000, .ri_uohm = 303000 },
|
||||
{ .vbat_uv = 3939000, .ri_uohm = 280000 },
|
||||
{ .vbat_uv = 3904000, .ri_uohm = 261000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 232000 },
|
||||
{ .vbat_uv = 3750000, .ri_uohm = 177000 },
|
||||
{ .vbat_uv = 3712000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 3674000, .ri_uohm = 161000 },
|
||||
{ .vbat_uv = 3590000, .ri_uohm = 164000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = {
|
||||
{ .vbat_uv = 4240000, .ri_uohm = 160000 },
|
||||
{ .vbat_uv = 4210000, .ri_uohm = 179000 },
|
||||
{ .vbat_uv = 4180000, .ri_uohm = 183000 },
|
||||
{ .vbat_uv = 4160000, .ri_uohm = 184000 },
|
||||
{ .vbat_uv = 4140000, .ri_uohm = 191000 },
|
||||
{ .vbat_uv = 4120000, .ri_uohm = 204000 },
|
||||
{ .vbat_uv = 4080000, .ri_uohm = 200000 },
|
||||
{ .vbat_uv = 4027000, .ri_uohm = 202000 },
|
||||
{ .vbat_uv = 3916000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3842000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 3742000, .ri_uohm = 263000 },
|
||||
{ .vbat_uv = 3709000, .ri_uohm = 277000 },
|
||||
{ .vbat_uv = 3685000, .ri_uohm = 312000 },
|
||||
{ .vbat_uv = 3668000, .ri_uohm = 258000 },
|
||||
{ .vbat_uv = 3660000, .ri_uohm = 247000 },
|
||||
{ .vbat_uv = 3636000, .ri_uohm = 293000 },
|
||||
{ .vbat_uv = 3616000, .ri_uohm = 331000 },
|
||||
{ .vbat_uv = 3600000, .ri_uohm = 349000 },
|
||||
{ .vbat_uv = 3593000, .ri_uohm = 345000 },
|
||||
{ .vbat_uv = 3585000, .ri_uohm = 344000 },
|
||||
{ .vbat_uv = 3572000, .ri_uohm = 336000 },
|
||||
{ .vbat_uv = 3553000, .ri_uohm = 321000 },
|
||||
{ .vbat_uv = 3517000, .ri_uohm = 336000 },
|
||||
{ .vbat_uv = 3503000, .ri_uohm = 322000 },
|
||||
{ .vbat_uv = 3400000, .ri_uohm = 269000 },
|
||||
{ .vbat_uv = 3360000, .ri_uohm = 328000 },
|
||||
{ .vbat_uv = 3330000, .ri_uohm = 305000 },
|
||||
{ .vbat_uv = 3300000, .ri_uohm = 339000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = {
|
||||
{ .vbat_uv = 4345000, .ri_uohm = 230000 },
|
||||
{ .vbat_uv = 4329000, .ri_uohm = 238000 },
|
||||
{ .vbat_uv = 4314000, .ri_uohm = 225000 },
|
||||
{ .vbat_uv = 4311000, .ri_uohm = 239000 },
|
||||
{ .vbat_uv = 4294000, .ri_uohm = 235000 },
|
||||
{ .vbat_uv = 4264000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 4262000, .ri_uohm = 228000 },
|
||||
{ .vbat_uv = 4252000, .ri_uohm = 236000 },
|
||||
{ .vbat_uv = 4244000, .ri_uohm = 234000 },
|
||||
{ .vbat_uv = 4235000, .ri_uohm = 234000 },
|
||||
{ .vbat_uv = 4227000, .ri_uohm = 238000 },
|
||||
{ .vbat_uv = 4219000, .ri_uohm = 242000 },
|
||||
{ .vbat_uv = 4212000, .ri_uohm = 239000 },
|
||||
{ .vbat_uv = 4206000, .ri_uohm = 231000 },
|
||||
{ .vbat_uv = 4201000, .ri_uohm = 231000 },
|
||||
{ .vbat_uv = 4192000, .ri_uohm = 224000 },
|
||||
{ .vbat_uv = 4184000, .ri_uohm = 238000 },
|
||||
{ .vbat_uv = 4173000, .ri_uohm = 245000 },
|
||||
{ .vbat_uv = 4161000, .ri_uohm = 244000 },
|
||||
{ .vbat_uv = 4146000, .ri_uohm = 244000 },
|
||||
{ .vbat_uv = 4127000, .ri_uohm = 228000 },
|
||||
{ .vbat_uv = 4119000, .ri_uohm = 218000 },
|
||||
{ .vbat_uv = 4112000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 4108000, .ri_uohm = 209000 },
|
||||
{ .vbat_uv = 4102000, .ri_uohm = 214000 },
|
||||
{ .vbat_uv = 4096000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 4090000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 4083000, .ri_uohm = 219000 },
|
||||
{ .vbat_uv = 4078000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 4071000, .ri_uohm = 205000 },
|
||||
{ .vbat_uv = 4066000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 4061000, .ri_uohm = 210000 },
|
||||
{ .vbat_uv = 4055000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 4049000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 4042000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 4032000, .ri_uohm = 217000 },
|
||||
{ .vbat_uv = 4027000, .ri_uohm = 220000 },
|
||||
{ .vbat_uv = 4020000, .ri_uohm = 210000 },
|
||||
{ .vbat_uv = 4013000, .ri_uohm = 214000 },
|
||||
{ .vbat_uv = 4007000, .ri_uohm = 219000 },
|
||||
{ .vbat_uv = 4003000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 3996000, .ri_uohm = 246000 },
|
||||
{ .vbat_uv = 3990000, .ri_uohm = 245000 },
|
||||
{ .vbat_uv = 3984000, .ri_uohm = 242000 },
|
||||
{ .vbat_uv = 3977000, .ri_uohm = 236000 },
|
||||
{ .vbat_uv = 3971000, .ri_uohm = 231000 },
|
||||
{ .vbat_uv = 3966000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 3952000, .ri_uohm = 226000 },
|
||||
{ .vbat_uv = 3946000, .ri_uohm = 222000 },
|
||||
{ .vbat_uv = 3941000, .ri_uohm = 222000 },
|
||||
{ .vbat_uv = 3936000, .ri_uohm = 217000 },
|
||||
{ .vbat_uv = 3932000, .ri_uohm = 217000 },
|
||||
{ .vbat_uv = 3928000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3926000, .ri_uohm = 214000 },
|
||||
{ .vbat_uv = 3922000, .ri_uohm = 209000 },
|
||||
{ .vbat_uv = 3917000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3914000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3912000, .ri_uohm = 220000 },
|
||||
{ .vbat_uv = 3910000, .ri_uohm = 226000 },
|
||||
{ .vbat_uv = 3903000, .ri_uohm = 226000 },
|
||||
{ .vbat_uv = 3891000, .ri_uohm = 222000 },
|
||||
{ .vbat_uv = 3871000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3857000, .ri_uohm = 219000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 216000 },
|
||||
{ .vbat_uv = 3843000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3835000, .ri_uohm = 206000 },
|
||||
{ .vbat_uv = 3825000, .ri_uohm = 217000 },
|
||||
{ .vbat_uv = 3824000, .ri_uohm = 220000 },
|
||||
{ .vbat_uv = 3820000, .ri_uohm = 237000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 232000 },
|
||||
{ .vbat_uv = 3750000, .ri_uohm = 177000 },
|
||||
{ .vbat_uv = 3712000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 3674000, .ri_uohm = 161000 },
|
||||
{ .vbat_uv = 3590000, .ri_uohm = 164000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = {
|
||||
{ .vbat_uv = 4240000, .ri_uohm = 160000 },
|
||||
{ .vbat_uv = 4210000, .ri_uohm = 179000 },
|
||||
{ .vbat_uv = 4180000, .ri_uohm = 183000 },
|
||||
{ .vbat_uv = 4160000, .ri_uohm = 184000 },
|
||||
{ .vbat_uv = 4140000, .ri_uohm = 191000 },
|
||||
{ .vbat_uv = 4120000, .ri_uohm = 204000 },
|
||||
{ .vbat_uv = 4080000, .ri_uohm = 200000 },
|
||||
{ .vbat_uv = 4027000, .ri_uohm = 202000 },
|
||||
{ .vbat_uv = 3916000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3842000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 3742000, .ri_uohm = 263000 },
|
||||
{ .vbat_uv = 3708000, .ri_uohm = 277000 },
|
||||
{ .vbat_uv = 3684000, .ri_uohm = 272000 },
|
||||
{ .vbat_uv = 3664000, .ri_uohm = 278000 },
|
||||
{ .vbat_uv = 3655000, .ri_uohm = 285000 },
|
||||
{ .vbat_uv = 3638000, .ri_uohm = 261000 },
|
||||
{ .vbat_uv = 3624000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3616000, .ri_uohm = 266000 },
|
||||
{ .vbat_uv = 3597000, .ri_uohm = 278000 },
|
||||
{ .vbat_uv = 3581000, .ri_uohm = 281000 },
|
||||
{ .vbat_uv = 3560000, .ri_uohm = 287000 },
|
||||
{ .vbat_uv = 3527000, .ri_uohm = 289000 },
|
||||
{ .vbat_uv = 3512000, .ri_uohm = 286000 },
|
||||
{ .vbat_uv = 3494000, .ri_uohm = 282000 },
|
||||
{ .vbat_uv = 3400000, .ri_uohm = 269000 },
|
||||
{ .vbat_uv = 3360000, .ri_uohm = 328000 },
|
||||
{ .vbat_uv = 3330000, .ri_uohm = 305000 },
|
||||
{ .vbat_uv = 3300000, .ri_uohm = 339000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = {
|
||||
{ .vbat_uv = 4346000, .ri_uohm = 293000 },
|
||||
{ .vbat_uv = 4336000, .ri_uohm = 290000 },
|
||||
{ .vbat_uv = 4315000, .ri_uohm = 274000 },
|
||||
{ .vbat_uv = 4310000, .ri_uohm = 264000 },
|
||||
{ .vbat_uv = 4275000, .ri_uohm = 275000 },
|
||||
{ .vbat_uv = 4267000, .ri_uohm = 274000 },
|
||||
{ .vbat_uv = 4227000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 4186000, .ri_uohm = 282000 },
|
||||
{ .vbat_uv = 4136000, .ri_uohm = 246000 },
|
||||
{ .vbat_uv = 4110000, .ri_uohm = 242000 },
|
||||
{ .vbat_uv = 4077000, .ri_uohm = 249000 },
|
||||
{ .vbat_uv = 4049000, .ri_uohm = 238000 },
|
||||
{ .vbat_uv = 4017000, .ri_uohm = 268000 },
|
||||
{ .vbat_uv = 3986000, .ri_uohm = 261000 },
|
||||
{ .vbat_uv = 3962000, .ri_uohm = 252000 },
|
||||
{ .vbat_uv = 3940000, .ri_uohm = 235000 },
|
||||
{ .vbat_uv = 3930000, .ri_uohm = 237000 },
|
||||
{ .vbat_uv = 3924000, .ri_uohm = 255000 },
|
||||
{ .vbat_uv = 3910000, .ri_uohm = 244000 },
|
||||
{ .vbat_uv = 3889000, .ri_uohm = 231000 },
|
||||
{ .vbat_uv = 3875000, .ri_uohm = 249000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 232000 },
|
||||
{ .vbat_uv = 3750000, .ri_uohm = 177000 },
|
||||
{ .vbat_uv = 3712000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 3674000, .ri_uohm = 161000 },
|
||||
{ .vbat_uv = 3590000, .ri_uohm = 164000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = {
|
||||
{ .vbat_uv = 4240000, .ri_uohm = 160000 },
|
||||
{ .vbat_uv = 4210000, .ri_uohm = 179000 },
|
||||
{ .vbat_uv = 4180000, .ri_uohm = 183000 },
|
||||
{ .vbat_uv = 4160000, .ri_uohm = 184000 },
|
||||
{ .vbat_uv = 4140000, .ri_uohm = 191000 },
|
||||
{ .vbat_uv = 4120000, .ri_uohm = 204000 },
|
||||
{ .vbat_uv = 4080000, .ri_uohm = 200000 },
|
||||
{ .vbat_uv = 4027000, .ri_uohm = 202000 },
|
||||
{ .vbat_uv = 3916000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3842000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 3715000, .ri_uohm = 340000 },
|
||||
{ .vbat_uv = 3700000, .ri_uohm = 300000 },
|
||||
{ .vbat_uv = 3682000, .ri_uohm = 233000 },
|
||||
{ .vbat_uv = 3655000, .ri_uohm = 246000 },
|
||||
{ .vbat_uv = 3639000, .ri_uohm = 260000 },
|
||||
{ .vbat_uv = 3621000, .ri_uohm = 254000 },
|
||||
{ .vbat_uv = 3583000, .ri_uohm = 266000 },
|
||||
{ .vbat_uv = 3536000, .ri_uohm = 274000 },
|
||||
{ .vbat_uv = 3502000, .ri_uohm = 300000 },
|
||||
{ .vbat_uv = 3465000, .ri_uohm = 245000 },
|
||||
{ .vbat_uv = 3438000, .ri_uohm = 225000 },
|
||||
{ .vbat_uv = 3330000, .ri_uohm = 305000 },
|
||||
{ .vbat_uv = 3300000, .ri_uohm = 339000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = {
|
||||
{ .vbat_uv = 4302000, .ri_uohm = 200000 },
|
||||
{ .vbat_uv = 4258000, .ri_uohm = 206000 },
|
||||
{ .vbat_uv = 4200000, .ri_uohm = 231000 },
|
||||
{ .vbat_uv = 4150000, .ri_uohm = 198000 },
|
||||
{ .vbat_uv = 4134000, .ri_uohm = 268000 },
|
||||
{ .vbat_uv = 4058000, .ri_uohm = 172000 },
|
||||
{ .vbat_uv = 4003000, .ri_uohm = 227000 },
|
||||
{ .vbat_uv = 3972000, .ri_uohm = 241000 },
|
||||
{ .vbat_uv = 3953000, .ri_uohm = 244000 },
|
||||
{ .vbat_uv = 3950000, .ri_uohm = 213000 },
|
||||
{ .vbat_uv = 3900000, .ri_uohm = 225000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 232000 },
|
||||
{ .vbat_uv = 3750000, .ri_uohm = 177000 },
|
||||
{ .vbat_uv = 3712000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 3674000, .ri_uohm = 161000 },
|
||||
{ .vbat_uv = 3590000, .ri_uohm = 164000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = {
|
||||
{ .vbat_uv = 4071000, .ri_uohm = 158000 },
|
||||
{ .vbat_uv = 4019000, .ri_uohm = 187000 },
|
||||
{ .vbat_uv = 3951000, .ri_uohm = 191000 },
|
||||
{ .vbat_uv = 3901000, .ri_uohm = 193000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 273000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 305000 },
|
||||
{ .vbat_uv = 3750000, .ri_uohm = 205000 },
|
||||
{ .vbat_uv = 3700000, .ri_uohm = 290000 },
|
||||
{ .vbat_uv = 3650000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 3618000, .ri_uohm = 290000 },
|
||||
{ .vbat_uv = 3505000, .ri_uohm = 235000 },
|
||||
{ .vbat_uv = 3484000, .ri_uohm = 253000 },
|
||||
{ .vbat_uv = 3413000, .ri_uohm = 243000 },
|
||||
{ .vbat_uv = 3393000, .ri_uohm = 285000 },
|
||||
{ .vbat_uv = 3361000, .ri_uohm = 281000 },
|
||||
{ .vbat_uv = 3302000, .ri_uohm = 286000 },
|
||||
{ .vbat_uv = 3280000, .ri_uohm = 250000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = {
|
||||
{ .vbat_uv = 4190000, .ri_uohm = 214000 },
|
||||
{ .vbat_uv = 4159000, .ri_uohm = 252000 },
|
||||
{ .vbat_uv = 4121000, .ri_uohm = 245000 },
|
||||
{ .vbat_uv = 4069000, .ri_uohm = 228000 },
|
||||
{ .vbat_uv = 4046000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 4026000, .ri_uohm = 233000 },
|
||||
{ .vbat_uv = 4007000, .ri_uohm = 240000 },
|
||||
{ .vbat_uv = 3982000, .ri_uohm = 291000 },
|
||||
{ .vbat_uv = 3945000, .ri_uohm = 276000 },
|
||||
{ .vbat_uv = 3924000, .ri_uohm = 266000 },
|
||||
{ .vbat_uv = 3910000, .ri_uohm = 258000 },
|
||||
{ .vbat_uv = 3900000, .ri_uohm = 271000 },
|
||||
{ .vbat_uv = 3844000, .ri_uohm = 279000 },
|
||||
{ .vbat_uv = 3772000, .ri_uohm = 217000 },
|
||||
{ .vbat_uv = 3673000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 3571000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 3510000, .ri_uohm = 228000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = {
|
||||
{ .vbat_uv = 4194000, .ri_uohm = 121000 },
|
||||
{ .vbat_uv = 4169000, .ri_uohm = 188000 },
|
||||
{ .vbat_uv = 4136000, .ri_uohm = 173000 },
|
||||
{ .vbat_uv = 4108000, .ri_uohm = 158000 },
|
||||
{ .vbat_uv = 4064000, .ri_uohm = 143000 },
|
||||
{ .vbat_uv = 3956000, .ri_uohm = 160000 },
|
||||
{ .vbat_uv = 3847000, .ri_uohm = 262000 },
|
||||
{ .vbat_uv = 3806000, .ri_uohm = 280000 },
|
||||
{ .vbat_uv = 3801000, .ri_uohm = 266000 },
|
||||
{ .vbat_uv = 3794000, .ri_uohm = 259000 },
|
||||
{ .vbat_uv = 3785000, .ri_uohm = 234000 },
|
||||
{ .vbat_uv = 3779000, .ri_uohm = 227000 },
|
||||
{ .vbat_uv = 3772000, .ri_uohm = 222000 },
|
||||
{ .vbat_uv = 3765000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3759000, .ri_uohm = 216000 },
|
||||
{ .vbat_uv = 3754000, .ri_uohm = 206000 },
|
||||
{ .vbat_uv = 3747000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3743000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 3737000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3733000, .ri_uohm = 200000 },
|
||||
{ .vbat_uv = 3728000, .ri_uohm = 203000 },
|
||||
{ .vbat_uv = 3722000, .ri_uohm = 207000 },
|
||||
{ .vbat_uv = 3719000, .ri_uohm = 208000 },
|
||||
{ .vbat_uv = 3715000, .ri_uohm = 209000 },
|
||||
{ .vbat_uv = 3712000, .ri_uohm = 211000 },
|
||||
{ .vbat_uv = 3709000, .ri_uohm = 210000 },
|
||||
{ .vbat_uv = 3704000, .ri_uohm = 216000 },
|
||||
{ .vbat_uv = 3701000, .ri_uohm = 218000 },
|
||||
{ .vbat_uv = 3698000, .ri_uohm = 222000 },
|
||||
{ .vbat_uv = 3694000, .ri_uohm = 218000 },
|
||||
{ .vbat_uv = 3692000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3688000, .ri_uohm = 224000 },
|
||||
{ .vbat_uv = 3686000, .ri_uohm = 224000 },
|
||||
{ .vbat_uv = 3683000, .ri_uohm = 228000 },
|
||||
{ .vbat_uv = 3681000, .ri_uohm = 228000 },
|
||||
{ .vbat_uv = 3679000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 3676000, .ri_uohm = 232000 },
|
||||
{ .vbat_uv = 3675000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 3673000, .ri_uohm = 229000 },
|
||||
{ .vbat_uv = 3672000, .ri_uohm = 223000 },
|
||||
{ .vbat_uv = 3669000, .ri_uohm = 224000 },
|
||||
{ .vbat_uv = 3666000, .ri_uohm = 224000 },
|
||||
{ .vbat_uv = 3663000, .ri_uohm = 221000 },
|
||||
{ .vbat_uv = 3660000, .ri_uohm = 218000 },
|
||||
{ .vbat_uv = 3657000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3654000, .ri_uohm = 212000 },
|
||||
{ .vbat_uv = 3649000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3644000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3636000, .ri_uohm = 215000 },
|
||||
{ .vbat_uv = 3631000, .ri_uohm = 206000 },
|
||||
{ .vbat_uv = 3623000, .ri_uohm = 205000 },
|
||||
{ .vbat_uv = 3616000, .ri_uohm = 193000 },
|
||||
{ .vbat_uv = 3605000, .ri_uohm = 193000 },
|
||||
{ .vbat_uv = 3600000, .ri_uohm = 198000 },
|
||||
{ .vbat_uv = 3597000, .ri_uohm = 198000 },
|
||||
{ .vbat_uv = 3592000, .ri_uohm = 203000 },
|
||||
{ .vbat_uv = 3591000, .ri_uohm = 188000 },
|
||||
{ .vbat_uv = 3587000, .ri_uohm = 188000 },
|
||||
{ .vbat_uv = 3583000, .ri_uohm = 177000 },
|
||||
{ .vbat_uv = 3577000, .ri_uohm = 170000 },
|
||||
{ .vbat_uv = 3568000, .ri_uohm = 135000 },
|
||||
{ .vbat_uv = 3552000, .ri_uohm = 54000 },
|
||||
{ .vbat_uv = 3526000, .ri_uohm = 130000 },
|
||||
{ .vbat_uv = 3501000, .ri_uohm = 48000 },
|
||||
{ .vbat_uv = 3442000, .ri_uohm = 183000 },
|
||||
{ .vbat_uv = 3326000, .ri_uohm = 372000 },
|
||||
{ .vbat_uv = 3161000, .ri_uohm = 452000 },
|
||||
};
|
||||
|
||||
static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = {
|
||||
{ .vbat_uv = 4360000, .ri_uohm = 128000 },
|
||||
{ .vbat_uv = 4325000, .ri_uohm = 130000 },
|
||||
{ .vbat_uv = 4316000, .ri_uohm = 148000 },
|
||||
{ .vbat_uv = 4308000, .ri_uohm = 162000 },
|
||||
{ .vbat_uv = 4301000, .ri_uohm = 162000 },
|
||||
{ .vbat_uv = 4250000, .ri_uohm = 162000 },
|
||||
{ .vbat_uv = 4230000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 4030000, .ri_uohm = 164000 },
|
||||
{ .vbat_uv = 4000000, .ri_uohm = 193000 },
|
||||
{ .vbat_uv = 3950000, .ri_uohm = 204000 },
|
||||
{ .vbat_uv = 3850000, .ri_uohm = 210000 },
|
||||
{ .vbat_uv = 3800000, .ri_uohm = 230000 },
|
||||
{ .vbat_uv = 3790000, .ri_uohm = 240000 },
|
||||
{ .vbat_uv = 3780000, .ri_uohm = 311000 },
|
||||
{ .vbat_uv = 3760000, .ri_uohm = 420000 },
|
||||
{ .vbat_uv = 3700000, .ri_uohm = 504000 },
|
||||
{ .vbat_uv = 3600000, .ri_uohm = 565000 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Temperature to internal resistance scaling tables.
|
||||
*
|
||||
* "resistance" is the percentage of the resistance determined from the voltage
|
||||
* so this represents the capacity ratio at different temperatures.
|
||||
*
|
||||
* FIXME: the proper table is missing: Samsung does not provide the necessary
|
||||
* temperature compensation tables so we just state 100% for every temperature.
|
||||
* If you have the datasheets, please provide these tables.
|
||||
*/
|
||||
static struct power_supply_resistance_temp_table samsung_temp2res[] = {
|
||||
{ .temp = 50, .resistance = 100 },
|
||||
{ .temp = 40, .resistance = 100 },
|
||||
{ .temp = 30, .resistance = 100 },
|
||||
{ .temp = 20, .resistance = 100 },
|
||||
{ .temp = 10, .resistance = 100 },
|
||||
{ .temp = 00, .resistance = 100 },
|
||||
{ .temp = -10, .resistance = 100 },
|
||||
{ .temp = -20, .resistance = 100 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Capacity tables for different Open Circuit Voltages (OCV).
|
||||
* These must be sorted by falling OCV value.
|
||||
*/
|
||||
|
||||
static struct power_supply_battery_ocv_table samsung_ocv_cap_eb485159lu[] = {
|
||||
{ .ocv = 4330000, .capacity = 100},
|
||||
{ .ocv = 4320000, .capacity = 99},
|
||||
{ .ocv = 4283000, .capacity = 95},
|
||||
{ .ocv = 4246000, .capacity = 92},
|
||||
{ .ocv = 4211000, .capacity = 89},
|
||||
{ .ocv = 4167000, .capacity = 85},
|
||||
{ .ocv = 4146000, .capacity = 83},
|
||||
{ .ocv = 4124000, .capacity = 81},
|
||||
{ .ocv = 4062000, .capacity = 75},
|
||||
{ .ocv = 4013000, .capacity = 70},
|
||||
{ .ocv = 3977000, .capacity = 66},
|
||||
{ .ocv = 3931000, .capacity = 60},
|
||||
{ .ocv = 3914000, .capacity = 58},
|
||||
{ .ocv = 3901000, .capacity = 57},
|
||||
{ .ocv = 3884000, .capacity = 56},
|
||||
{ .ocv = 3870000, .capacity = 55},
|
||||
{ .ocv = 3862000, .capacity = 54},
|
||||
{ .ocv = 3854000, .capacity = 53},
|
||||
{ .ocv = 3838000, .capacity = 50},
|
||||
{ .ocv = 3823000, .capacity = 47},
|
||||
{ .ocv = 3813000, .capacity = 45},
|
||||
{ .ocv = 3807000, .capacity = 43},
|
||||
{ .ocv = 3800000, .capacity = 41},
|
||||
{ .ocv = 3795000, .capacity = 40},
|
||||
{ .ocv = 3786000, .capacity = 37},
|
||||
{ .ocv = 3783000, .capacity = 35},
|
||||
{ .ocv = 3773000, .capacity = 30},
|
||||
{ .ocv = 3758000, .capacity = 25},
|
||||
{ .ocv = 3745000, .capacity = 22},
|
||||
{ .ocv = 3738000, .capacity = 20},
|
||||
{ .ocv = 3733000, .capacity = 19},
|
||||
{ .ocv = 3716000, .capacity = 17},
|
||||
{ .ocv = 3709000, .capacity = 16},
|
||||
{ .ocv = 3698000, .capacity = 15},
|
||||
{ .ocv = 3687000, .capacity = 14},
|
||||
{ .ocv = 3684000, .capacity = 13},
|
||||
{ .ocv = 3684000, .capacity = 12},
|
||||
{ .ocv = 3678000, .capacity = 10},
|
||||
{ .ocv = 3671000, .capacity = 9},
|
||||
{ .ocv = 3665000, .capacity = 8},
|
||||
{ .ocv = 3651000, .capacity = 7},
|
||||
{ .ocv = 3634000, .capacity = 6},
|
||||
{ .ocv = 3601000, .capacity = 5},
|
||||
{ .ocv = 3564000, .capacity = 4},
|
||||
{ .ocv = 3516000, .capacity = 3},
|
||||
{ .ocv = 3456000, .capacity = 2},
|
||||
{ .ocv = 3381000, .capacity = 1},
|
||||
{ .ocv = 3300000, .capacity = 0},
|
||||
};
|
||||
|
||||
/* Same capacity table is used by eb-l1m7flu, eb425161la, eb425161lu */
|
||||
static struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = {
|
||||
{ .ocv = 4328000, .capacity = 100},
|
||||
{ .ocv = 4299000, .capacity = 99},
|
||||
{ .ocv = 4281000, .capacity = 98},
|
||||
{ .ocv = 4241000, .capacity = 95},
|
||||
{ .ocv = 4183000, .capacity = 90},
|
||||
{ .ocv = 4150000, .capacity = 87},
|
||||
{ .ocv = 4116000, .capacity = 84},
|
||||
{ .ocv = 4077000, .capacity = 80},
|
||||
{ .ocv = 4068000, .capacity = 79},
|
||||
{ .ocv = 4058000, .capacity = 77},
|
||||
{ .ocv = 4026000, .capacity = 75},
|
||||
{ .ocv = 3987000, .capacity = 72},
|
||||
{ .ocv = 3974000, .capacity = 69},
|
||||
{ .ocv = 3953000, .capacity = 66},
|
||||
{ .ocv = 3933000, .capacity = 63},
|
||||
{ .ocv = 3911000, .capacity = 60},
|
||||
{ .ocv = 3900000, .capacity = 58},
|
||||
{ .ocv = 3873000, .capacity = 55},
|
||||
{ .ocv = 3842000, .capacity = 52},
|
||||
{ .ocv = 3829000, .capacity = 50},
|
||||
{ .ocv = 3810000, .capacity = 45},
|
||||
{ .ocv = 3793000, .capacity = 40},
|
||||
{ .ocv = 3783000, .capacity = 35},
|
||||
{ .ocv = 3776000, .capacity = 30},
|
||||
{ .ocv = 3762000, .capacity = 25},
|
||||
{ .ocv = 3746000, .capacity = 20},
|
||||
{ .ocv = 3739000, .capacity = 18},
|
||||
{ .ocv = 3715000, .capacity = 15},
|
||||
{ .ocv = 3700000, .capacity = 12},
|
||||
{ .ocv = 3690000, .capacity = 10},
|
||||
{ .ocv = 3680000, .capacity = 9},
|
||||
{ .ocv = 3670000, .capacity = 7},
|
||||
{ .ocv = 3656000, .capacity = 5},
|
||||
{ .ocv = 3634000, .capacity = 4},
|
||||
{ .ocv = 3614000, .capacity = 3},
|
||||
{ .ocv = 3551000, .capacity = 2},
|
||||
{ .ocv = 3458000, .capacity = 1},
|
||||
{ .ocv = 3300000, .capacity = 0},
|
||||
};
|
||||
|
||||
static struct power_supply_battery_ocv_table samsung_ocv_cap_eb535151vu[] = {
|
||||
{ .ocv = 4178000, .capacity = 100},
|
||||
{ .ocv = 4148000, .capacity = 99},
|
||||
{ .ocv = 4105000, .capacity = 95},
|
||||
{ .ocv = 4078000, .capacity = 92},
|
||||
{ .ocv = 4057000, .capacity = 89},
|
||||
{ .ocv = 4013000, .capacity = 85},
|
||||
{ .ocv = 3988000, .capacity = 82},
|
||||
{ .ocv = 3962000, .capacity = 77},
|
||||
{ .ocv = 3920000, .capacity = 70},
|
||||
{ .ocv = 3891000, .capacity = 65},
|
||||
{ .ocv = 3874000, .capacity = 62},
|
||||
{ .ocv = 3839000, .capacity = 59},
|
||||
{ .ocv = 3816000, .capacity = 55},
|
||||
{ .ocv = 3798000, .capacity = 50},
|
||||
{ .ocv = 3778000, .capacity = 40},
|
||||
{ .ocv = 3764000, .capacity = 30},
|
||||
{ .ocv = 3743000, .capacity = 25},
|
||||
{ .ocv = 3711000, .capacity = 20},
|
||||
{ .ocv = 3691000, .capacity = 18},
|
||||
{ .ocv = 3685000, .capacity = 15},
|
||||
{ .ocv = 3680000, .capacity = 12},
|
||||
{ .ocv = 3662000, .capacity = 10},
|
||||
{ .ocv = 3638000, .capacity = 9},
|
||||
{ .ocv = 3593000, .capacity = 7},
|
||||
{ .ocv = 3566000, .capacity = 6},
|
||||
{ .ocv = 3497000, .capacity = 4},
|
||||
{ .ocv = 3405000, .capacity = 2},
|
||||
{ .ocv = 3352000, .capacity = 1},
|
||||
{ .ocv = 3300000, .capacity = 0},
|
||||
};
|
||||
|
||||
static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = {
|
||||
{ .ocv = 4320000, .capacity = 100},
|
||||
{ .ocv = 4296000, .capacity = 99},
|
||||
{ .ocv = 4283000, .capacity = 98},
|
||||
{ .ocv = 4245000, .capacity = 95},
|
||||
{ .ocv = 4185000, .capacity = 90},
|
||||
{ .ocv = 4152000, .capacity = 87},
|
||||
{ .ocv = 4119000, .capacity = 84},
|
||||
{ .ocv = 4077000, .capacity = 80},
|
||||
{ .ocv = 4057000, .capacity = 78},
|
||||
{ .ocv = 4048000, .capacity = 77},
|
||||
{ .ocv = 4020000, .capacity = 74},
|
||||
{ .ocv = 4003000, .capacity = 72},
|
||||
{ .ocv = 3978000, .capacity = 69},
|
||||
{ .ocv = 3955000, .capacity = 66},
|
||||
{ .ocv = 3934000, .capacity = 63},
|
||||
{ .ocv = 3912000, .capacity = 60},
|
||||
{ .ocv = 3894000, .capacity = 58},
|
||||
{ .ocv = 3860000, .capacity = 55},
|
||||
{ .ocv = 3837000, .capacity = 52},
|
||||
{ .ocv = 3827000, .capacity = 50},
|
||||
{ .ocv = 3806000, .capacity = 45},
|
||||
{ .ocv = 3791000, .capacity = 40},
|
||||
{ .ocv = 3779000, .capacity = 35},
|
||||
{ .ocv = 3770000, .capacity = 30},
|
||||
{ .ocv = 3758000, .capacity = 25},
|
||||
{ .ocv = 3739000, .capacity = 20},
|
||||
{ .ocv = 3730000, .capacity = 18},
|
||||
{ .ocv = 3706000, .capacity = 15},
|
||||
{ .ocv = 3684000, .capacity = 13},
|
||||
{ .ocv = 3675000, .capacity = 10},
|
||||
{ .ocv = 3673000, .capacity = 9},
|
||||
{ .ocv = 3665000, .capacity = 7},
|
||||
{ .ocv = 3649000, .capacity = 5},
|
||||
{ .ocv = 3628000, .capacity = 4},
|
||||
{ .ocv = 3585000, .capacity = 3},
|
||||
{ .ocv = 3525000, .capacity = 2},
|
||||
{ .ocv = 3441000, .capacity = 1},
|
||||
{ .ocv = 3300000, .capacity = 0},
|
||||
};
|
||||
|
||||
static struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = {
|
||||
{
|
||||
/* Maintenance charging phase A, 60 hours */
|
||||
.charge_current_max_ua = 600000,
|
||||
.charge_voltage_max_uv = 4150000,
|
||||
.charge_safety_timer_minutes = 60*60,
|
||||
},
|
||||
{
|
||||
/* Maintenance charging phase B, 200 hours */
|
||||
.charge_current_max_ua = 600000,
|
||||
.charge_voltage_max_uv = 4100000,
|
||||
.charge_safety_timer_minutes = 200*60,
|
||||
}
|
||||
};
|
||||
|
||||
static struct samsung_sdi_battery samsung_sdi_batteries[] = {
|
||||
{
|
||||
/*
|
||||
* Used in Samsung GT-I8190 "Golden"
|
||||
* Data from vendor boardfile board-golden-[bm|battery].c
|
||||
*/
|
||||
.compatible = "samsung,eb-l1m7flu",
|
||||
.name = "EB-L1M7FLU",
|
||||
.info = {
|
||||
.charge_full_design_uah = 1500000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 100000,
|
||||
.factory_internal_resistance_charging_uohm = 200000,
|
||||
/* If you have data on this fix the min_design_uv */
|
||||
.voltage_min_design_uv = 3320000,
|
||||
.voltage_max_design_uv = 4340000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 900000,
|
||||
.constant_charge_voltage_max_uv = 4320000,
|
||||
.charge_term_current_ua = 200000,
|
||||
.charge_restart_voltage_uv = 4300000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -50,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 40,
|
||||
.temp_max = 60,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_1500mah,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb_l1m7flu,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb_l1m7flu),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb_l1m7flu,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb_l1m7flu),
|
||||
.bti_resistance_ohm = 2400,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Used in Samsung SGH-T599 "Codina TMO" and SGH-I407 "Kyle"
|
||||
* Data from vendor boardfile board-kyle-[bm|battery].c
|
||||
*/
|
||||
.compatible = "samsung,eb425161la",
|
||||
.name = "EB425161LA",
|
||||
.info = {
|
||||
.charge_full_design_uah = 1500000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 136000,
|
||||
.factory_internal_resistance_charging_uohm = 200000,
|
||||
/* If you have data on this fix the min_design_uv */
|
||||
.voltage_min_design_uv = 3320000,
|
||||
.voltage_max_design_uv = 4340000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 900000,
|
||||
.constant_charge_voltage_max_uv = 4320000,
|
||||
.charge_term_current_ua = 200000,
|
||||
.charge_restart_voltage_uv = 4270000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -30,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 40,
|
||||
.temp_max = 47,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_1500mah,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb425161la,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb425161la),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb425161la,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb425161la),
|
||||
.bti_resistance_ohm = 2400,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Used in Samsung GT-I8160 "Codina"
|
||||
* Data from vendor boardfile board-codina-[bm|battery].c
|
||||
*/
|
||||
.compatible = "samsung,eb425161lu",
|
||||
.name = "EB425161LU",
|
||||
.info = {
|
||||
.charge_full_design_uah = 1500000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 100000,
|
||||
.factory_internal_resistance_charging_uohm = 200000,
|
||||
/* If you have data on this fix the min_design_uv */
|
||||
.voltage_min_design_uv = 3320000,
|
||||
.voltage_max_design_uv = 4350000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 900000,
|
||||
.constant_charge_voltage_max_uv = 4340000,
|
||||
.charge_term_current_ua = 200000,
|
||||
.charge_restart_voltage_uv = 4280000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -50,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 43,
|
||||
.temp_max = 49,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_1500mah,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb425161lu,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb425161lu),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb425161lu,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb425161lu),
|
||||
.bti_resistance_ohm = 2400,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Used in Samsung GT-S7710 "Skomer"
|
||||
* Data from vendor boardfile board-skomer-[bm|battery].c
|
||||
*/
|
||||
.compatible = "samsung,eb485159lu",
|
||||
.name = "EB485159LU",
|
||||
.info = {
|
||||
.charge_full_design_uah = 1700000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 100000,
|
||||
.factory_internal_resistance_charging_uohm = 200000,
|
||||
.voltage_min_design_uv = 3320000,
|
||||
.voltage_max_design_uv = 4350000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 900000,
|
||||
.constant_charge_voltage_max_uv = 4340000,
|
||||
.charge_term_current_ua = 200000,
|
||||
.charge_restart_voltage_uv = 4300000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -50,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 40,
|
||||
.temp_max = 60,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_eb485159lu,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb485159lu),
|
||||
/* CHECKME: vendor uses the 1500 mAh table, check against datasheet */
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb485159lu,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb485159lu),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb485159lu,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb485159lu),
|
||||
.bti_resistance_ohm = 2400,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Used in Samsung GT-I9070 "Janice"
|
||||
* Data from vendor boardfile board-janice-bm.c
|
||||
*/
|
||||
.compatible = "samsung,eb535151vu",
|
||||
.name = "EB535151VU",
|
||||
.info = {
|
||||
.charge_full_design_uah = 1500000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 100000,
|
||||
.factory_internal_resistance_charging_uohm = 200000,
|
||||
/* If you have data on this fix the min_design_uv */
|
||||
.voltage_min_design_uv = 3300000,
|
||||
.voltage_max_design_uv = 4180000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 900000,
|
||||
.constant_charge_voltage_max_uv = 4200000,
|
||||
.charge_term_current_ua = 200000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -5,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 40,
|
||||
.temp_max = 60,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_eb535151vu,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb535151vu),
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb535151vu,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb535151vu),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb535151vu,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb535151vu),
|
||||
.bti_resistance_ohm = 1500,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Used in Samsung GT-I8530 "Gavini"
|
||||
* Data from vendor boardfile board-gavini-bm.c
|
||||
*/
|
||||
.compatible = "samsung,eb585157lu",
|
||||
.name = "EB585157LU",
|
||||
.info = {
|
||||
.charge_full_design_uah = 2000000,
|
||||
.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.factory_internal_resistance_uohm = 105000,
|
||||
.factory_internal_resistance_charging_uohm = 160000,
|
||||
/* If you have data on this fix the min_design_uv */
|
||||
.voltage_min_design_uv = 3300000,
|
||||
.voltage_max_design_uv = 4320000,
|
||||
.overvoltage_limit_uv = 4500000,
|
||||
.constant_charge_current_max_ua = 1500000,
|
||||
.constant_charge_voltage_max_uv = 4350000,
|
||||
.charge_term_current_ua = 120000,
|
||||
.maintenance_charge = samsung_maint_charge_table,
|
||||
.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
|
||||
.alert_low_temp_charge_current_ua = 300000,
|
||||
.alert_low_temp_charge_voltage_uv = 4000000,
|
||||
.alert_high_temp_charge_current_ua = 300000,
|
||||
.alert_high_temp_charge_voltage_uv = 4000000,
|
||||
.temp_min = -5,
|
||||
.temp_alert_min = 0,
|
||||
.temp_alert_max = 40,
|
||||
.temp_max = 60,
|
||||
.resist_table = samsung_temp2res,
|
||||
.resist_table_size = ARRAY_SIZE(samsung_temp2res),
|
||||
/* If you have tables for more temperatures, add them */
|
||||
.ocv_temp[0] = 25,
|
||||
.ocv_table[0] = samsung_ocv_cap_eb585157lu,
|
||||
.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb585157lu),
|
||||
.vbat2ri_discharging = samsung_vbat2res_discharging_eb585157lu,
|
||||
.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb585157lu),
|
||||
.vbat2ri_charging = samsung_vbat2res_charging_eb585157lu,
|
||||
.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb585157lu),
|
||||
.bti_resistance_ohm = 2400,
|
||||
.bti_resistance_tolerance = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
int samsung_sdi_battery_get_info(struct device *dev,
|
||||
const char *compatible,
|
||||
struct power_supply_battery_info **info)
|
||||
{
|
||||
struct samsung_sdi_battery *batt;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(samsung_sdi_batteries); i++) {
|
||||
batt = &samsung_sdi_batteries[i];
|
||||
if (!strcmp(compatible, batt->compatible))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(samsung_sdi_batteries))
|
||||
return -ENODEV;
|
||||
|
||||
*info = &batt->info;
|
||||
dev_info(dev, "Samsung SDI %s battery %d mAh\n",
|
||||
batt->name, batt->info.charge_full_design_uah / 1000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_sdi_battery_get_info);
|
13
drivers/power/supply/samsung-sdi-battery.h
Normal file
13
drivers/power/supply/samsung-sdi-battery.h
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG_SDI)
|
||||
extern int samsung_sdi_battery_get_info(struct device *dev,
|
||||
const char *compatible,
|
||||
struct power_supply_battery_info **info);
|
||||
#else
|
||||
static inline int samsung_sdi_battery_get_info(struct device *dev,
|
||||
const char *compatible,
|
||||
struct power_supply_battery_info **info)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
@ -18,6 +18,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
|
||||
#define SBS_CHARGER_REG_SPEC_INFO 0x11
|
||||
#define SBS_CHARGER_REG_STATUS 0x13
|
||||
@ -209,7 +210,12 @@ static int sbs_probe(struct i2c_client *client,
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to request irq\n");
|
||||
} else {
|
||||
INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
|
||||
ret = devm_delayed_work_autocancel(&client->dev, &chip->work,
|
||||
sbs_delayed_work);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to init work for polling\n");
|
||||
|
||||
schedule_delayed_work(&chip->work,
|
||||
msecs_to_jiffies(SBS_CHARGER_POLL_TIME));
|
||||
}
|
||||
@ -220,15 +226,6 @@ static int sbs_probe(struct i2c_client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sbs_remove(struct i2c_client *client)
|
||||
{
|
||||
struct sbs_info *chip = i2c_get_clientdata(client);
|
||||
|
||||
cancel_delayed_work_sync(&chip->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id sbs_dt_ids[] = {
|
||||
{ .compatible = "sbs,sbs-charger" },
|
||||
@ -245,7 +242,6 @@ MODULE_DEVICE_TABLE(i2c, sbs_id);
|
||||
|
||||
static struct i2c_driver sbs_driver = {
|
||||
.probe = sbs_probe,
|
||||
.remove = sbs_remove,
|
||||
.id_table = sbs_id,
|
||||
.driver = {
|
||||
.name = "sbs-charger",
|
||||
|
@ -1488,8 +1488,7 @@ static const struct regmap_config smb347_regmap = {
|
||||
.max_register = SMB347_MAX_REGISTER,
|
||||
.volatile_reg = smb347_volatile_reg,
|
||||
.readable_reg = smb347_readable_reg,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
.num_reg_defaults_raw = SMB347_MAX_REGISTER,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static const struct regulator_ops smb347_usb_vbus_regulator_ops = {
|
||||
|
486
drivers/power/supply/ug3105_battery.c
Normal file
486
drivers/power/supply/ug3105_battery.c
Normal file
@ -0,0 +1,486 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Battery monitor driver for the uPI uG3105 battery monitor
|
||||
*
|
||||
* Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is
|
||||
* expected to be use in combination with some always on microcontroller reading
|
||||
* its coulomb-counter before it can wrap (must be read every 400 seconds!).
|
||||
*
|
||||
* Since Linux does not monitor coulomb-counter changes while the device
|
||||
* is off or suspended, the coulomb counter is not used atm.
|
||||
*
|
||||
* Possible improvements:
|
||||
* 1. Activate commented out total_coulomb_count code
|
||||
* 2. Reset total_coulomb_count val to 0 when the battery is as good as empty
|
||||
* and remember that we did this (and clear the flag for this on susp/resume)
|
||||
* 3. When the battery is full check if the flag that we set total_coulomb_count
|
||||
* to when the battery was empty is set. If so we now know the capacity,
|
||||
* not the design, but actual capacity, of the battery
|
||||
* 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember
|
||||
* the actual capacity of the battery over reboots
|
||||
* 5. When we know the actual capacity at probe time, add energy_now and
|
||||
* energy_full attributes. Guess boot + resume energy_now value based on ocv
|
||||
* and then use total_coulomb_count to report energy_now over time, resetting
|
||||
* things to adjust for drift when empty/full. This should give more accurate
|
||||
* readings, esp. in the 30-70% range and allow userspace to estimate time
|
||||
* remaining till empty/full
|
||||
* 6. Maybe unregister + reregister the psy device when we learn the actual
|
||||
* capacity during run-time ?
|
||||
*
|
||||
* The above will also require some sort of mwh_per_unit calculation. Testing
|
||||
* has shown that an estimated 7404mWh increase of the battery's energy results
|
||||
* in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R.
|
||||
*
|
||||
* Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
|
||||
*/
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define UG3105_MOV_AVG_WINDOW 8
|
||||
#define UG3105_INIT_POLL_TIME (5 * HZ)
|
||||
#define UG3105_POLL_TIME (30 * HZ)
|
||||
#define UG3105_SETTLE_TIME (1 * HZ)
|
||||
|
||||
#define UG3105_INIT_POLL_COUNT 30
|
||||
|
||||
#define UG3105_REG_MODE 0x00
|
||||
#define UG3105_REG_CTRL1 0x01
|
||||
#define UG3105_REG_COULOMB_CNT 0x02
|
||||
#define UG3105_REG_BAT_VOLT 0x08
|
||||
#define UG3105_REG_BAT_CURR 0x0c
|
||||
|
||||
#define UG3105_MODE_STANDBY 0x00
|
||||
#define UG3105_MODE_RUN 0x10
|
||||
|
||||
#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03
|
||||
|
||||
#define UG3105_CURR_HYST_UA 65000
|
||||
|
||||
#define UG3105_LOW_BAT_UV 3700000
|
||||
#define UG3105_FULL_BAT_HYST_UV 38000
|
||||
|
||||
struct ug3105_chip {
|
||||
struct i2c_client *client;
|
||||
struct power_supply *psy;
|
||||
struct power_supply_battery_info *info;
|
||||
struct delayed_work work;
|
||||
struct mutex lock;
|
||||
int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */
|
||||
int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */
|
||||
int poll_count;
|
||||
int ocv_avg_index;
|
||||
int ocv_avg; /* micro-volt */
|
||||
int intern_res_poll_count;
|
||||
int intern_res_avg_index;
|
||||
int intern_res_avg; /* milli-ohm */
|
||||
int volt; /* micro-volt */
|
||||
int curr; /* micro-ampere */
|
||||
int total_coulomb_count;
|
||||
int uv_per_unit;
|
||||
int ua_per_unit;
|
||||
int status;
|
||||
int capacity;
|
||||
bool supplied;
|
||||
};
|
||||
|
||||
static int ug3105_read_word(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = i2c_smbus_read_word_data(client, reg);
|
||||
if (val < 0)
|
||||
dev_err(&client->dev, "Error reading reg 0x%02x\n", reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int ug3105_get_status(struct ug3105_chip *chip)
|
||||
{
|
||||
int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV;
|
||||
|
||||
if (chip->curr > UG3105_CURR_HYST_UA)
|
||||
return POWER_SUPPLY_STATUS_CHARGING;
|
||||
|
||||
if (chip->curr < -UG3105_CURR_HYST_UA)
|
||||
return POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
if (chip->supplied && chip->ocv_avg > full)
|
||||
return POWER_SUPPLY_STATUS_FULL;
|
||||
|
||||
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
}
|
||||
|
||||
static int ug3105_get_capacity(struct ug3105_chip *chip)
|
||||
{
|
||||
/*
|
||||
* OCV voltages in uV for 0-110% in 5% increments, the 100-110% is
|
||||
* for LiPo HV (High-Voltage) bateries which can go up to 4.35V
|
||||
* instead of the usual 4.2V.
|
||||
*/
|
||||
static const int ocv_capacity_tbl[23] = {
|
||||
3350000,
|
||||
3610000,
|
||||
3690000,
|
||||
3710000,
|
||||
3730000,
|
||||
3750000,
|
||||
3770000,
|
||||
3786667,
|
||||
3803333,
|
||||
3820000,
|
||||
3836667,
|
||||
3853333,
|
||||
3870000,
|
||||
3907500,
|
||||
3945000,
|
||||
3982500,
|
||||
4020000,
|
||||
4075000,
|
||||
4110000,
|
||||
4150000,
|
||||
4200000,
|
||||
4250000,
|
||||
4300000,
|
||||
};
|
||||
int i, ocv_diff, ocv_step;
|
||||
|
||||
if (chip->ocv_avg < ocv_capacity_tbl[0])
|
||||
return 0;
|
||||
|
||||
if (chip->status == POWER_SUPPLY_STATUS_FULL)
|
||||
return 100;
|
||||
|
||||
for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) {
|
||||
if (chip->ocv_avg > ocv_capacity_tbl[i])
|
||||
continue;
|
||||
|
||||
ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg;
|
||||
ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1];
|
||||
/* scale 0-110% down to 0-100% for LiPo HV */
|
||||
if (chip->info->constant_charge_voltage_max_uv >= 4300000)
|
||||
return (i * 500 - ocv_diff * 500 / ocv_step) / 110;
|
||||
else
|
||||
return i * 5 - ocv_diff * 5 / ocv_step;
|
||||
}
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
static void ug3105_work(struct work_struct *work)
|
||||
{
|
||||
struct ug3105_chip *chip = container_of(work, struct ug3105_chip,
|
||||
work.work);
|
||||
int i, val, curr_diff, volt_diff, res, win_size;
|
||||
bool prev_supplied = chip->supplied;
|
||||
int prev_status = chip->status;
|
||||
int prev_volt = chip->volt;
|
||||
int prev_curr = chip->curr;
|
||||
struct power_supply *psy;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
psy = chip->psy;
|
||||
if (!psy)
|
||||
goto out;
|
||||
|
||||
val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
|
||||
if (val < 0)
|
||||
goto out;
|
||||
chip->volt = val * chip->uv_per_unit;
|
||||
|
||||
val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
|
||||
if (val < 0)
|
||||
goto out;
|
||||
chip->curr = (s16)val * chip->ua_per_unit;
|
||||
|
||||
chip->ocv[chip->ocv_avg_index] =
|
||||
chip->volt - chip->curr * chip->intern_res_avg / 1000;
|
||||
chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
|
||||
chip->poll_count++;
|
||||
|
||||
/*
|
||||
* See possible improvements comment above.
|
||||
*
|
||||
* Read + reset coulomb counter every 10 polls (every 300 seconds)
|
||||
* if ((chip->poll_count % 10) == 0) {
|
||||
* val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
|
||||
* if (val < 0)
|
||||
* goto out;
|
||||
*
|
||||
* i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
|
||||
* UG3105_CTRL1_RESET_COULOMB_CNT);
|
||||
*
|
||||
* chip->total_coulomb_count += (s16)val;
|
||||
* dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
|
||||
* (s16)val, chip->total_coulomb_count);
|
||||
* }
|
||||
*/
|
||||
|
||||
chip->ocv_avg = 0;
|
||||
win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW);
|
||||
for (i = 0; i < win_size; i++)
|
||||
chip->ocv_avg += chip->ocv[i];
|
||||
chip->ocv_avg /= win_size;
|
||||
|
||||
chip->supplied = power_supply_am_i_supplied(psy);
|
||||
chip->status = ug3105_get_status(chip);
|
||||
chip->capacity = ug3105_get_capacity(chip);
|
||||
|
||||
/*
|
||||
* Skip internal resistance calc on charger [un]plug and
|
||||
* when the battery is almost empty (voltage low).
|
||||
*/
|
||||
if (chip->supplied != prev_supplied ||
|
||||
chip->volt < UG3105_LOW_BAT_UV ||
|
||||
chip->poll_count < 2)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Assuming that the OCV voltage does not change significantly
|
||||
* between 2 polls, then we can calculate the internal resistance
|
||||
* on a significant current change by attributing all voltage
|
||||
* change between the 2 readings to the internal resistance.
|
||||
*/
|
||||
curr_diff = abs(chip->curr - prev_curr);
|
||||
if (curr_diff < UG3105_CURR_HYST_UA)
|
||||
goto out;
|
||||
|
||||
volt_diff = abs(chip->volt - prev_volt);
|
||||
res = volt_diff * 1000 / curr_diff;
|
||||
|
||||
if ((res < (chip->intern_res_avg * 2 / 3)) ||
|
||||
(res > (chip->intern_res_avg * 4 / 3))) {
|
||||
dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res);
|
||||
|
||||
chip->intern_res[chip->intern_res_avg_index] = res;
|
||||
chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
|
||||
chip->intern_res_poll_count++;
|
||||
|
||||
chip->intern_res_avg = 0;
|
||||
win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW);
|
||||
for (i = 0; i < win_size; i++)
|
||||
chip->intern_res_avg += chip->intern_res[i];
|
||||
chip->intern_res_avg /= win_size;
|
||||
|
||||
out:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
queue_delayed_work(system_wq, &chip->work,
|
||||
(chip->poll_count <= UG3105_INIT_POLL_COUNT) ?
|
||||
UG3105_INIT_POLL_TIME : UG3105_POLL_TIME);
|
||||
|
||||
if (chip->status != prev_status && psy)
|
||||
power_supply_changed(psy);
|
||||
}
|
||||
|
||||
static enum power_supply_property ug3105_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_TECHNOLOGY,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_OCV,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
};
|
||||
|
||||
static int ug3105_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct ug3105_chip *chip = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
if (!chip->psy) {
|
||||
ret = -EAGAIN;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
val->intval = chip->status;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
||||
val->intval = chip->info->technology;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
|
||||
if (ret < 0)
|
||||
break;
|
||||
val->intval = ret * chip->uv_per_unit;
|
||||
ret = 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
|
||||
val->intval = chip->ocv_avg;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
|
||||
if (ret < 0)
|
||||
break;
|
||||
val->intval = (s16)ret * chip->ua_per_unit;
|
||||
ret = 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = chip->capacity;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ug3105_external_power_changed(struct power_supply *psy)
|
||||
{
|
||||
struct ug3105_chip *chip = power_supply_get_drvdata(psy);
|
||||
|
||||
dev_dbg(&chip->client->dev, "external power changed\n");
|
||||
mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
|
||||
}
|
||||
|
||||
static const struct power_supply_desc ug3105_psy_desc = {
|
||||
.name = "ug3105_battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.get_property = ug3105_get_property,
|
||||
.external_power_changed = ug3105_external_power_changed,
|
||||
.properties = ug3105_battery_props,
|
||||
.num_properties = ARRAY_SIZE(ug3105_battery_props),
|
||||
};
|
||||
|
||||
static void ug3105_init(struct ug3105_chip *chip)
|
||||
{
|
||||
chip->poll_count = 0;
|
||||
chip->ocv_avg_index = 0;
|
||||
chip->total_coulomb_count = 0;
|
||||
i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
|
||||
UG3105_MODE_RUN);
|
||||
i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
|
||||
UG3105_CTRL1_RESET_COULOMB_CNT);
|
||||
queue_delayed_work(system_wq, &chip->work, 0);
|
||||
flush_delayed_work(&chip->work);
|
||||
}
|
||||
|
||||
static int ug3105_probe(struct i2c_client *client)
|
||||
{
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct device *dev = &client->dev;
|
||||
u32 curr_sense_res_uohm = 10000;
|
||||
struct power_supply *psy;
|
||||
struct ug3105_chip *chip;
|
||||
int ret;
|
||||
|
||||
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->client = client;
|
||||
mutex_init(&chip->lock);
|
||||
ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
psy_cfg.drv_data = chip;
|
||||
psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
|
||||
if (IS_ERR(psy))
|
||||
return PTR_ERR(psy);
|
||||
|
||||
ret = power_supply_get_battery_info(psy, &chip->info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (chip->info->factory_internal_resistance_uohm == -EINVAL ||
|
||||
chip->info->constant_charge_voltage_max_uv == -EINVAL) {
|
||||
dev_err(dev, "error required properties are missing\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm);
|
||||
|
||||
/*
|
||||
* DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10
|
||||
* coming from somewhere for some reason (verified with a volt-meter).
|
||||
*/
|
||||
chip->uv_per_unit = 45000000/65536;
|
||||
/* Datasheet says 8.1 uV per unit for the current ADC */
|
||||
chip->ua_per_unit = 8100000 / curr_sense_res_uohm;
|
||||
|
||||
/* Use provided internal resistance as start point (in milli-ohm) */
|
||||
chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000;
|
||||
/* Also add it to the internal resistance moving average window */
|
||||
chip->intern_res[0] = chip->intern_res_avg;
|
||||
chip->intern_res_avg_index = 1;
|
||||
chip->intern_res_poll_count = 1;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
chip->psy = psy;
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
ug3105_init(chip);
|
||||
|
||||
i2c_set_clientdata(client, chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ug3105_suspend(struct device *dev)
|
||||
{
|
||||
struct ug3105_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
cancel_delayed_work_sync(&chip->work);
|
||||
i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
|
||||
UG3105_MODE_STANDBY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ug3105_resume(struct device *dev)
|
||||
{
|
||||
struct ug3105_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
ug3105_init(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend,
|
||||
ug3105_resume);
|
||||
|
||||
static const struct i2c_device_id ug3105_id[] = {
|
||||
{ "ug3105" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ug3105_id);
|
||||
|
||||
static struct i2c_driver ug3105_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "ug3105",
|
||||
.pm = &ug3105_pm_ops,
|
||||
},
|
||||
.probe_new = ug3105_probe,
|
||||
.id_table = ug3105_id,
|
||||
};
|
||||
module_i2c_driver(ug3105_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
|
||||
MODULE_DESCRIPTION("uPI uG3105 battery monitor driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -408,44 +408,112 @@ static const struct power_supply_desc wm8350_usb_desc = {
|
||||
* Initialisation
|
||||
*********************************************************************/
|
||||
|
||||
static void wm8350_init_charger(struct wm8350 *wm8350)
|
||||
static int wm8350_init_charger(struct wm8350 *wm8350)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* register our interest in charger events */
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
|
||||
wm8350_charger_handler, 0, "Battery hot", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
|
||||
wm8350_charger_handler, 0, "Battery cold", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
|
||||
if (ret)
|
||||
goto free_chg_bat_hot;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
|
||||
wm8350_charger_handler, 0, "Battery fail", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
|
||||
if (ret)
|
||||
goto free_chg_bat_cold;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
|
||||
wm8350_charger_handler, 0,
|
||||
"Charger timeout", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
|
||||
if (ret)
|
||||
goto free_chg_bat_fail;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
|
||||
wm8350_charger_handler, 0,
|
||||
"Charge end", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
|
||||
if (ret)
|
||||
goto free_chg_to;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
|
||||
wm8350_charger_handler, 0,
|
||||
"Charge start", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
|
||||
if (ret)
|
||||
goto free_chg_end;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
|
||||
wm8350_charger_handler, 0,
|
||||
"Fast charge ready", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
|
||||
if (ret)
|
||||
goto free_chg_start;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
|
||||
wm8350_charger_handler, 0,
|
||||
"Battery <3.9V", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
|
||||
if (ret)
|
||||
goto free_chg_fast_rdy;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
|
||||
wm8350_charger_handler, 0,
|
||||
"Battery <3.1V", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
|
||||
if (ret)
|
||||
goto free_chg_vbatt_lt_3p9;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
|
||||
wm8350_charger_handler, 0,
|
||||
"Battery <2.85V", wm8350);
|
||||
if (ret)
|
||||
goto free_chg_vbatt_lt_3p1;
|
||||
|
||||
/* and supply change events */
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
|
||||
wm8350_charger_handler, 0, "USB", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
|
||||
if (ret)
|
||||
goto free_chg_vbatt_lt_2p85;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
|
||||
wm8350_charger_handler, 0, "Wall", wm8350);
|
||||
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
|
||||
if (ret)
|
||||
goto free_ext_usb_fb;
|
||||
|
||||
ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
|
||||
wm8350_charger_handler, 0, "Battery", wm8350);
|
||||
if (ret)
|
||||
goto free_ext_wall_fb;
|
||||
|
||||
return 0;
|
||||
|
||||
free_ext_wall_fb:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
|
||||
free_ext_usb_fb:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
|
||||
free_chg_vbatt_lt_2p85:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
|
||||
free_chg_vbatt_lt_3p1:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
|
||||
free_chg_vbatt_lt_3p9:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
|
||||
free_chg_fast_rdy:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350);
|
||||
free_chg_start:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
|
||||
free_chg_end:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
|
||||
free_chg_to:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
|
||||
free_chg_bat_fail:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
|
||||
free_chg_bat_cold:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
|
||||
free_chg_bat_hot:
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_charger_irq(struct wm8350 *wm8350)
|
||||
@ -456,6 +524,7 @@ static void free_charger_irq(struct wm8350 *wm8350)
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
|
||||
|
@ -13,6 +13,13 @@
|
||||
|
||||
#include <linux/regmap.h>
|
||||
|
||||
enum intel_cht_wc_models {
|
||||
INTEL_CHT_WC_UNKNOWN,
|
||||
INTEL_CHT_WC_GPD_WIN_POCKET,
|
||||
INTEL_CHT_WC_XIAOMI_MIPAD2,
|
||||
INTEL_CHT_WC_LENOVO_YOGABOOK1,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct intel_soc_pmic - Intel SoC PMIC data
|
||||
* @irq: Master interrupt number of the parent PMIC device
|
||||
@ -39,6 +46,7 @@ struct intel_soc_pmic {
|
||||
struct regmap_irq_chip_data *irq_chip_data_crit;
|
||||
struct device *dev;
|
||||
struct intel_scu_ipc_dev *scu;
|
||||
enum intel_cht_wc_models cht_wc_model;
|
||||
};
|
||||
|
||||
int intel_soc_pmic_exec_mipi_pmic_seq_element(u16 i2c_address, u32 reg_address,
|
||||
|
@ -3386,6 +3386,9 @@ enum ec_mkbp_event {
|
||||
/* Send an incoming CEC message to the AP */
|
||||
EC_MKBP_EVENT_CEC_MESSAGE = 9,
|
||||
|
||||
/* Peripheral device charger event */
|
||||
EC_MKBP_EVENT_PCHG = 12,
|
||||
|
||||
/* Number of MKBP events */
|
||||
EC_MKBP_EVENT_COUNT,
|
||||
};
|
||||
@ -5527,6 +5530,67 @@ enum pchg_state {
|
||||
[PCHG_STATE_CONNECTED] = "CONNECTED", \
|
||||
}
|
||||
|
||||
/*
|
||||
* Update firmware of peripheral chip
|
||||
*/
|
||||
#define EC_CMD_PCHG_UPDATE 0x0136
|
||||
|
||||
/* Port number is encoded in bit[28:31]. */
|
||||
#define EC_MKBP_PCHG_PORT_SHIFT 28
|
||||
/* Utility macro for converting MKBP event to port number. */
|
||||
#define EC_MKBP_PCHG_EVENT_TO_PORT(e) (((e) >> EC_MKBP_PCHG_PORT_SHIFT) & 0xf)
|
||||
/* Utility macro for extracting event bits. */
|
||||
#define EC_MKBP_PCHG_EVENT_MASK(e) ((e) \
|
||||
& GENMASK(EC_MKBP_PCHG_PORT_SHIFT-1, 0))
|
||||
|
||||
#define EC_MKBP_PCHG_UPDATE_OPENED BIT(0)
|
||||
#define EC_MKBP_PCHG_WRITE_COMPLETE BIT(1)
|
||||
#define EC_MKBP_PCHG_UPDATE_CLOSED BIT(2)
|
||||
#define EC_MKBP_PCHG_UPDATE_ERROR BIT(3)
|
||||
#define EC_MKBP_PCHG_DEVICE_EVENT BIT(4)
|
||||
|
||||
enum ec_pchg_update_cmd {
|
||||
/* Reset chip to normal mode. */
|
||||
EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL = 0,
|
||||
/* Reset and put a chip in update (a.k.a. download) mode. */
|
||||
EC_PCHG_UPDATE_CMD_OPEN,
|
||||
/* Write a block of data containing FW image. */
|
||||
EC_PCHG_UPDATE_CMD_WRITE,
|
||||
/* Close update session. */
|
||||
EC_PCHG_UPDATE_CMD_CLOSE,
|
||||
/* End of commands */
|
||||
EC_PCHG_UPDATE_CMD_COUNT,
|
||||
};
|
||||
|
||||
struct ec_params_pchg_update {
|
||||
/* PCHG port number */
|
||||
uint8_t port;
|
||||
/* enum ec_pchg_update_cmd */
|
||||
uint8_t cmd;
|
||||
/* Padding */
|
||||
uint8_t reserved0;
|
||||
uint8_t reserved1;
|
||||
/* Version of new firmware */
|
||||
uint32_t version;
|
||||
/* CRC32 of new firmware */
|
||||
uint32_t crc32;
|
||||
/* Address in chip memory where <data> is written to */
|
||||
uint32_t addr;
|
||||
/* Size of <data> */
|
||||
uint32_t size;
|
||||
/* Partial data of new firmware */
|
||||
uint8_t data[];
|
||||
} __ec_align4;
|
||||
|
||||
BUILD_ASSERT(EC_PCHG_UPDATE_CMD_COUNT
|
||||
< BIT(sizeof(((struct ec_params_pchg_update *)0)->cmd)*8));
|
||||
|
||||
struct ec_response_pchg_update {
|
||||
/* Block size */
|
||||
uint32_t block_size;
|
||||
} __ec_align4;
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Voltage regulator controls */
|
||||
|
||||
|
15
include/linux/power/bq25890_charger.h
Normal file
15
include/linux/power/bq25890_charger.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Platform data for the TI bq25890 battery charger driver.
|
||||
*/
|
||||
|
||||
#ifndef _BQ25890_CHARGER_H_
|
||||
#define _BQ25890_CHARGER_H_
|
||||
|
||||
struct regulator_init_data;
|
||||
|
||||
struct bq25890_platform_data {
|
||||
const struct regulator_init_data *regulator_init_data;
|
||||
};
|
||||
|
||||
#endif
|
@ -49,6 +49,7 @@ enum {
|
||||
POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE, /* dynamically adjusted speed */
|
||||
POWER_SUPPLY_CHARGE_TYPE_CUSTOM, /* use CHARGE_CONTROL_* props */
|
||||
POWER_SUPPLY_CHARGE_TYPE_LONGLIFE, /* slow speed, longer life */
|
||||
POWER_SUPPLY_CHARGE_TYPE_BYPASS, /* bypassing the charger */
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -348,6 +349,57 @@ struct power_supply_resistance_temp_table {
|
||||
int resistance; /* internal resistance percent */
|
||||
};
|
||||
|
||||
struct power_supply_vbat_ri_table {
|
||||
int vbat_uv; /* Battery voltage in microvolt */
|
||||
int ri_uohm; /* Internal resistance in microohm */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct power_supply_maintenance_charge_table - setting for maintenace charging
|
||||
* @charge_current_max_ua: maintenance charging current that is used to keep
|
||||
* the charge of the battery full as current is consumed after full charging.
|
||||
* The corresponding charge_voltage_max_uv is used as a safeguard: when we
|
||||
* reach this voltage the maintenance charging current is turned off. It is
|
||||
* turned back on if we fall below this voltage.
|
||||
* @charge_voltage_max_uv: maintenance charging voltage that is usually a bit
|
||||
* lower than the constant_charge_voltage_max_uv. We can apply this settings
|
||||
* charge_current_max_ua until we get back up to this voltage.
|
||||
* @safety_timer_minutes: maintenance charging safety timer, with an expiry
|
||||
* time in minutes. We will only use maintenance charging in this setting
|
||||
* for a certain amount of time, then we will first move to the next
|
||||
* maintenance charge current and voltage pair in respective array and wait
|
||||
* for the next safety timer timeout, or, if we reached the last maintencance
|
||||
* charging setting, disable charging until we reach
|
||||
* charge_restart_voltage_uv and restart ordinary CC/CV charging from there.
|
||||
* These timers should be chosen to align with the typical discharge curve
|
||||
* for the battery.
|
||||
*
|
||||
* When the main CC/CV charging is complete the battery can optionally be
|
||||
* maintenance charged at the voltages from this table: a table of settings is
|
||||
* traversed using a slightly lower current and voltage than what is used for
|
||||
* CC/CV charging. The maintenance charging will for safety reasons not go on
|
||||
* indefinately: we lower the current and voltage with successive maintenance
|
||||
* settings, then disable charging completely after we reach the last one,
|
||||
* and after that we do not restart charging until we reach
|
||||
* charge_restart_voltage_uv (see struct power_supply_battery_info) and restart
|
||||
* ordinary CC/CV charging from there.
|
||||
*
|
||||
* As an example, a Samsung EB425161LA Lithium-Ion battery is CC/CV charged
|
||||
* at 900mA to 4340mV, then maintenance charged at 600mA and 4150mV for
|
||||
* 60 hours, then maintenance charged at 600mA and 4100mV for 200 hours.
|
||||
* After this the charge cycle is restarted waiting for
|
||||
* charge_restart_voltage_uv.
|
||||
*
|
||||
* For most mobile electronics this type of maintenance charging is enough for
|
||||
* the user to disconnect the device and make use of it before both maintenance
|
||||
* charging cycles are complete.
|
||||
*/
|
||||
struct power_supply_maintenance_charge_table {
|
||||
int charge_current_max_ua;
|
||||
int charge_voltage_max_uv;
|
||||
int charge_safety_timer_minutes;
|
||||
};
|
||||
|
||||
#define POWER_SUPPLY_OCV_TEMP_MAX 20
|
||||
|
||||
/**
|
||||
@ -393,10 +445,34 @@ struct power_supply_resistance_temp_table {
|
||||
* @constant_charge_voltage_max_uv: voltage in microvolts signifying the end of
|
||||
* the CC (constant current) charging phase and the beginning of the CV
|
||||
* (constant voltage) charging phase.
|
||||
* @maintenance_charge: an array of maintenance charging settings to be used
|
||||
* after the main CC/CV charging phase is complete.
|
||||
* @maintenance_charge_size: the number of maintenance charging settings in
|
||||
* maintenance_charge.
|
||||
* @alert_low_temp_charge_current_ua: The charging current to use if the battery
|
||||
* enters low alert temperature, i.e. if the internal temperature is between
|
||||
* temp_alert_min and temp_min. No matter the charging phase, this
|
||||
* and alert_high_temp_charge_voltage_uv will be applied.
|
||||
* @alert_low_temp_charge_voltage_uv: Same as alert_low_temp_charge_current_ua,
|
||||
* but for the charging voltage.
|
||||
* @alert_high_temp_charge_current_ua: The charging current to use if the
|
||||
* battery enters high alert temperature, i.e. if the internal temperature is
|
||||
* between temp_alert_max and temp_max. No matter the charging phase, this
|
||||
* and alert_high_temp_charge_voltage_uv will be applied, usually lowering
|
||||
* the charging current as an evasive manouver.
|
||||
* @alert_high_temp_charge_voltage_uv: Same as
|
||||
* alert_high_temp_charge_current_ua, but for the charging voltage.
|
||||
* @factory_internal_resistance_uohm: the internal resistance of the battery
|
||||
* at fabrication time, expressed in microohms. This resistance will vary
|
||||
* depending on the lifetime and charge of the battery, so this is just a
|
||||
* nominal ballpark figure.
|
||||
* nominal ballpark figure. This internal resistance is given for the state
|
||||
* when the battery is discharging.
|
||||
* @factory_internal_resistance_charging_uohm: the internal resistance of the
|
||||
* battery at fabrication time while charging, expressed in microohms.
|
||||
* The charging process will affect the internal resistance of the battery
|
||||
* so this value provides a better resistance under these circumstances.
|
||||
* This resistance will vary depending on the lifetime and charge of the
|
||||
* battery, so this is just a nominal ballpark figure.
|
||||
* @ocv_temp: array indicating the open circuit voltage (OCV) capacity
|
||||
* temperature indices. This is an array of temperatures in degrees Celsius
|
||||
* indicating which capacity table to use for a certain temperature, since
|
||||
@ -434,13 +510,38 @@ struct power_supply_resistance_temp_table {
|
||||
* by temperature: highest temperature with lowest resistance first, lowest
|
||||
* temperature with highest resistance last.
|
||||
* @resist_table_size: the number of items in the resist_table.
|
||||
* @vbat2ri_discharging: this is a table that correlates Battery voltage (VBAT)
|
||||
* to internal resistance (Ri). The resistance is given in microohm for the
|
||||
* corresponding voltage in microvolts. The internal resistance is used to
|
||||
* determine the open circuit voltage so that we can determine the capacity
|
||||
* of the battery. These voltages to resistance tables apply when the battery
|
||||
* is discharging. The table must be ordered descending by voltage: highest
|
||||
* voltage first.
|
||||
* @vbat2ri_discharging_size: the number of items in the vbat2ri_discharging
|
||||
* table.
|
||||
* @vbat2ri_charging: same function as vbat2ri_discharging but for the state
|
||||
* when the battery is charging. Being under charge changes the battery's
|
||||
* internal resistance characteristics so a separate table is needed.*
|
||||
* The table must be ordered descending by voltage: highest voltage first.
|
||||
* @vbat2ri_charging_size: the number of items in the vbat2ri_charging
|
||||
* table.
|
||||
* @bti_resistance_ohm: The Battery Type Indicator (BIT) nominal resistance
|
||||
* in ohms for this battery, if an identification resistor is mounted
|
||||
* between a third battery terminal and ground. This scheme is used by a lot
|
||||
* of mobile device batteries.
|
||||
* @bti_resistance_tolerance: The tolerance in percent of the BTI resistance,
|
||||
* for example 10 for +/- 10%, if the bti_resistance is set to 7000 and the
|
||||
* tolerance is 10% we will detect a proper battery if the BTI resistance
|
||||
* is between 6300 and 7700 Ohm.
|
||||
*
|
||||
* 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.
|
||||
* The default field value is -EINVAL or NULL for pointers.
|
||||
*
|
||||
* CC/CV CHARGING:
|
||||
*
|
||||
* The charging parameters here assume a CC/CV charging scheme. This method
|
||||
* is most common with Lithium Ion batteries (other methods are possible) and
|
||||
@ -525,6 +626,66 @@ struct power_supply_resistance_temp_table {
|
||||
* Overcharging Lithium Ion cells can be DANGEROUS and lead to fire or
|
||||
* explosions.
|
||||
*
|
||||
* DETERMINING BATTERY CAPACITY:
|
||||
*
|
||||
* Several members of the struct deal with trying to determine the remaining
|
||||
* capacity in the battery, usually as a percentage of charge. In practice
|
||||
* many chargers uses a so-called fuel gauge or coloumb counter that measure
|
||||
* how much charge goes into the battery and how much goes out (+/- leak
|
||||
* consumption). This does not help if we do not know how much capacity the
|
||||
* battery has to begin with, such as when it is first used or was taken out
|
||||
* and charged in a separate charger. Therefore many capacity algorithms use
|
||||
* the open circuit voltage with a look-up table to determine the rough
|
||||
* capacity of the battery. The open circuit voltage can be conceptualized
|
||||
* with an ideal voltage source (V) in series with an internal resistance (Ri)
|
||||
* like this:
|
||||
*
|
||||
* +-------> IBAT >----------------+
|
||||
* | ^ |
|
||||
* [ ] Ri | |
|
||||
* | | VBAT |
|
||||
* o <---------- | |
|
||||
* +| ^ | [ ] Rload
|
||||
* .---. | | |
|
||||
* | V | | OCV | |
|
||||
* '---' | | |
|
||||
* | | | |
|
||||
* GND +-------------------------------+
|
||||
*
|
||||
* If we disconnect the load (here simplified as a fixed resistance Rload)
|
||||
* and measure VBAT with a infinite impedance voltage meter we will get
|
||||
* VBAT = OCV and this assumption is sometimes made even under load, assuming
|
||||
* Rload is insignificant. However this will be of dubious quality because the
|
||||
* load is rarely that small and Ri is strongly nonlinear depending on
|
||||
* temperature and how much capacity is left in the battery due to the
|
||||
* chemistry involved.
|
||||
*
|
||||
* In many practical applications we cannot just disconnect the battery from
|
||||
* the load, so instead we often try to measure the instantaneous IBAT (the
|
||||
* current out from the battery), estimate the Ri and thus calculate the
|
||||
* voltage drop over Ri and compensate like this:
|
||||
*
|
||||
* OCV = VBAT - (IBAT * Ri)
|
||||
*
|
||||
* The tables vbat2ri_discharging and vbat2ri_charging are used to determine
|
||||
* (by interpolation) the Ri from the VBAT under load. These curves are highly
|
||||
* nonlinear and may need many datapoints but can be found in datasheets for
|
||||
* some batteries. This gives the compensated open circuit voltage (OCV) for
|
||||
* the battery even under load. Using this method will also compensate for
|
||||
* temperature changes in the environment: this will also make the internal
|
||||
* resistance change, and it will affect the VBAT under load, so correlating
|
||||
* VBAT to Ri takes both remaining capacity and temperature into consideration.
|
||||
*
|
||||
* Alternatively a manufacturer can specify how the capacity of the battery
|
||||
* is dependent on the battery temperature which is the main factor affecting
|
||||
* Ri. As we know all checmical reactions are faster when it is warm and slower
|
||||
* when it is cold. You can put in 1500mAh and only get 800mAh out before the
|
||||
* voltage drops too low for example. This effect is also highly nonlinear and
|
||||
* the purpose of the table resist_table: this will take a temperature and
|
||||
* tell us how big percentage of Ri the specified temperature correlates to.
|
||||
* Usually we have 100% of the factory_internal_resistance_uohm at 25 degrees
|
||||
* Celsius.
|
||||
*
|
||||
* The power supply class itself doesn't use this struct as of now.
|
||||
*/
|
||||
|
||||
@ -542,7 +703,14 @@ struct power_supply_battery_info {
|
||||
int overvoltage_limit_uv;
|
||||
int constant_charge_current_max_ua;
|
||||
int constant_charge_voltage_max_uv;
|
||||
struct power_supply_maintenance_charge_table *maintenance_charge;
|
||||
int maintenance_charge_size;
|
||||
int alert_low_temp_charge_current_ua;
|
||||
int alert_low_temp_charge_voltage_uv;
|
||||
int alert_high_temp_charge_current_ua;
|
||||
int alert_high_temp_charge_voltage_uv;
|
||||
int factory_internal_resistance_uohm;
|
||||
int factory_internal_resistance_charging_uohm;
|
||||
int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];
|
||||
int temp_ambient_alert_min;
|
||||
int temp_ambient_alert_max;
|
||||
@ -554,6 +722,12 @@ struct power_supply_battery_info {
|
||||
int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX];
|
||||
struct power_supply_resistance_temp_table *resist_table;
|
||||
int resist_table_size;
|
||||
struct power_supply_vbat_ri_table *vbat2ri_discharging;
|
||||
int vbat2ri_discharging_size;
|
||||
struct power_supply_vbat_ri_table *vbat2ri_charging;
|
||||
int vbat2ri_charging_size;
|
||||
int bti_resistance_ohm;
|
||||
int bti_resistance_tolerance;
|
||||
};
|
||||
|
||||
extern struct atomic_notifier_head power_supply_notifier;
|
||||
@ -595,12 +769,43 @@ extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
|
||||
extern int
|
||||
power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
|
||||
int table_len, int temp);
|
||||
extern int power_supply_vbat2ri(struct power_supply_battery_info *info,
|
||||
int vbat_uv, bool charging);
|
||||
extern struct power_supply_maintenance_charge_table *
|
||||
power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index);
|
||||
extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info,
|
||||
int resistance);
|
||||
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_input_current_limit_from_supplier(
|
||||
struct power_supply *psy);
|
||||
int power_supply_get_property_from_supplier(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val);
|
||||
extern int power_supply_set_battery_charged(struct power_supply *psy);
|
||||
|
||||
static inline bool
|
||||
power_supply_supports_maintenance_charging(struct power_supply_battery_info *info)
|
||||
{
|
||||
struct power_supply_maintenance_charge_table *mt;
|
||||
|
||||
mt = power_supply_get_maintenance_charging_setting(info, 0);
|
||||
|
||||
return (mt != NULL);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
power_supply_supports_vbat2ri(struct power_supply_battery_info *info)
|
||||
{
|
||||
return ((info->vbat2ri_discharging != NULL) &&
|
||||
info->vbat2ri_discharging_size > 0);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
power_supply_supports_temp2ri(struct power_supply_battery_info *info)
|
||||
{
|
||||
return ((info->resist_table != NULL) &&
|
||||
info->resist_table_size > 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_POWER_SUPPLY
|
||||
extern int power_supply_is_system_supplied(void);
|
||||
#else
|
||||
|
Loading…
Reference in New Issue
Block a user