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:
Linus Torvalds 2022-03-25 13:31:02 -07:00
commit 8eb48fc7c5
50 changed files with 4137 additions and 1448 deletions

View File

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

View File

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

View File

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

View File

@ -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,.*":

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &param);
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, &param);
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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[] = {

View File

@ -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[] = {

View 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);

View 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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View 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

View File

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