* [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration
2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
` (2 preceding siblings ...)
2021-11-16 12:25 ` [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Matti Vaittinen
@ 2021-11-16 12:26 ` Matti Vaittinen
2021-11-19 1:42 ` Linus Walleij
2021-11-16 12:27 ` [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo Matti Vaittinen
` (4 subsequent siblings)
8 siblings, 1 reply; 34+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:26 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen
Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
linux-kernel, linux-power
[-- Attachment #1: Type: text/plain, Size: 15577 bytes --]
In some cases it is beneficial to be able to query the static battery
node properties prior power_supply registration. The device-tree
parsing does not really depend on psy so add APIs which can
be used without the power-supply. Also, convert APIs to operate on
fwnode while at it.
Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
RFCv3:
- New patch
---
drivers/power/supply/power_supply_core.c | 279 ++++++++++++++---------
include/linux/power_supply.h | 5 +
2 files changed, 176 insertions(+), 108 deletions(-)
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 1a21f692ab81..47176ed2570b 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -562,16 +562,63 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
#endif /* CONFIG_OF */
+struct psy_int_tuple {
+ int a;
+ int b;
+};
+
+static int get_fwnode_tuple_array(struct device *dev, struct fwnode_handle *fw,
+ const char *name,
+ struct psy_int_tuple **tuple, int *num_tuple)
+{
+ int num_values, i, ret;
+ u32 *tmp_table;
+
+ num_values = fwnode_property_count_u32(fw, name);
+ if (num_values <= 0) {
+ dev_err(dev, "failed to get %s\n", name);
+ return -EINVAL;
+ }
+
+ if (num_values & 0x1)
+ dev_warn(dev, "odd number of '%s' values\n", name);
+
+ tmp_table = kcalloc(num_values, sizeof(*tmp_table), GFP_KERNEL);
+ if (!tmp_table)
+ return -ENOMEM;
+
+ *tuple = devm_kcalloc(dev, num_values / 2, sizeof(**tuple),
+ GFP_KERNEL);
+ if (!*tuple) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ ret = fwnode_property_read_u32_array(fw, name, tmp_table, num_values);
+ if (ret)
+ goto out;
+
+ *num_tuple = num_values / 2;
+ for (i = 0; i < *num_tuple; i++) {
+ (*tuple)[i].a = tmp_table[i * 2];
+ (*tuple)[i].b = tmp_table[i * 2 + 1];
+ }
+
+out:
+ kfree(tmp_table);
+
+ return ret;
+}
+
#define POWER_SUPPLY_TEMP_DGRD_MAX_VALUES 100
-int power_supply_get_battery_info(struct power_supply *psy,
- struct power_supply_battery_info *info)
+int power_supply_dev_get_battery_info(struct device *dev,
+ struct fwnode_handle *node,
+ struct power_supply_battery_info *info)
{
- struct power_supply_resistance_temp_table *resist_table;
u32 *dgrd_table;
- struct device_node *battery_np;
- const char *value;
+ struct fwnode_handle *battery_node;
int err, len, index;
- const __be32 *list;
+ const char *value;
+ u32 tuple[2];
info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
info->energy_full_design_uwh = -EINVAL;
@@ -599,21 +646,23 @@ 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;
- }
+ if (!node)
+ node = dev_fwnode(dev);
- battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
- if (!battery_np)
+ if (!node) {
+ dev_err(dev, "no charger node\n");
return -ENODEV;
+ }
- err = of_property_read_string(battery_np, "compatible", &value);
- if (err)
- goto out_put_node;
+ battery_node = fwnode_find_reference(node, "monitored-battery", 0);
+ if (IS_ERR(battery_node)) {
+ dev_err(dev, "No battery node found\n");
+ return PTR_ERR(battery_node);
+ }
+
+ if (fwnode_property_match_string(battery_node, "compatible",
+ "simple-battery")) {
- if (strcmp("simple-battery", value)) {
err = -ENODEV;
goto out_put_node;
}
@@ -622,8 +671,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
* 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(battery_node, "device-chemistry", &value)) {
if (!strcmp("nickel-cadmium", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
else if (!strcmp("nickel-metal-hydride", value))
@@ -638,67 +686,73 @@ int power_supply_get_battery_info(struct power_supply *psy,
else if (!strcmp("lithium-ion-manganese-oxide", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
else
- dev_warn(&psy->dev, "%s unknown battery type\n", value);
+ dev_warn(dev, "%s unknown battery type\n", value);
}
- of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
+ fwnode_property_read_u32(battery_node, "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(battery_node, "charge-full-design-microamp-hours",
&info->charge_full_design_uah);
- of_property_read_u32(battery_np, "voltage-min-design-microvolt",
+ fwnode_property_read_u32(battery_node, "voltage-min-design-microvolt",
&info->voltage_min_design_uv);
- of_property_read_u32(battery_np, "voltage-max-design-microvolt",
+ fwnode_property_read_u32(battery_node, "voltage-max-design-microvolt",
&info->voltage_max_design_uv);
- of_property_read_u32(battery_np, "trickle-charge-current-microamp",
+ fwnode_property_read_u32(battery_node, "trickle-charge-current-microamp",
&info->tricklecharge_current_ua);
- of_property_read_u32(battery_np, "precharge-current-microamp",
+ fwnode_property_read_u32(battery_node, "precharge-current-microamp",
&info->precharge_current_ua);
- of_property_read_u32(battery_np, "precharge-upper-limit-microvolt",
+ fwnode_property_read_u32(battery_node, "precharge-upper-limit-microvolt",
&info->precharge_voltage_max_uv);
- of_property_read_u32(battery_np, "charge-term-current-microamp",
+ fwnode_property_read_u32(battery_node, "charge-term-current-microamp",
&info->charge_term_current_ua);
- of_property_read_u32(battery_np, "re-charge-voltage-microvolt",
+ fwnode_property_read_u32(battery_node, "re-charge-voltage-microvolt",
&info->charge_restart_voltage_uv);
- of_property_read_u32(battery_np, "over-voltage-threshold-microvolt",
+ fwnode_property_read_u32(battery_node, "over-voltage-threshold-microvolt",
&info->overvoltage_limit_uv);
- of_property_read_u32(battery_np, "constant-charge-current-max-microamp",
+ fwnode_property_read_u32(battery_node, "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(battery_node, "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(battery_node, "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);
-
- len = of_property_count_u32_elems(battery_np, "temp-degrade-table");
+ if (!fwnode_property_read_u32_array(battery_node, "ambient-celsius",
+ &tuple[0], 2)) {
+ info->temp_ambient_alert_min = tuple[0];
+ info->temp_ambient_alert_max = tuple[1];
+ }
+
+ if (!fwnode_property_read_u32_array(battery_node, "alert-celsius",
+ &tuple[0], 2)) {
+ info->temp_alert_min = tuple[0];
+ info->temp_alert_max = tuple[1];
+ }
+
+ if (!fwnode_property_read_u32_array(battery_node,
+ "operating-range-celsius",
+ &tuple[0], 2)) {
+ info->temp_min = tuple[0];
+ info->temp_max = tuple[1];
+ }
+
+ len = fwnode_property_count_u32(battery_node, "temp-degrade-table");
if (len == -EINVAL)
len = 0;
if (len < 0) {
+ dev_err(dev, "malformed temp-degrade-table %d\n", len);
err = len;
goto out_put_node;
}
- /* table should consist of value pairs - maximum of 100 pairs */
+ /* table should consist of value triplets - maximum of 100 triplets */
if (len % 3 || len / 3 > POWER_SUPPLY_TEMP_DGRD_MAX_VALUES) {
- dev_warn(&psy->dev,
+ dev_warn(dev,
"bad amount of temperature-capacity degrade values\n");
err = -EINVAL;
goto out_put_node;
}
info->temp_dgrd_values = len / 3;
if (info->temp_dgrd_values) {
- info->temp_dgrd = devm_kcalloc(&psy->dev,
- info->temp_dgrd_values,
+ info->temp_dgrd = devm_kcalloc(dev, info->temp_dgrd_values,
sizeof(*info->temp_dgrd),
GFP_KERNEL);
if (!info->temp_dgrd) {
@@ -710,12 +764,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = -ENOMEM;
goto out_put_node;
}
- err = of_property_read_u32_array(battery_np,
- "temp-degrade-table",
- dgrd_table, len);
+ err = fwnode_property_read_u32_array(battery_node,
+ "temp-degrade-table",
+ dgrd_table, len);
if (err) {
- dev_warn(&psy->dev,
- "bad temperature - capacity degrade values %d\n", err);
+ dev_warn(dev,
+ "bad temperature - capacity degrade values %d\n",
+ err);
kfree(dgrd_table);
info->temp_dgrd_values = 0;
goto out_put_node;
@@ -730,92 +785,100 @@ int power_supply_get_battery_info(struct power_supply *psy,
kfree(dgrd_table);
}
- len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
- if (len < 0 && len != -EINVAL) {
+ len = fwnode_property_count_u32(battery_node, "ocv-capacity-celsius");
+ if (len == -EINVAL)
+ len = 0;
+
+ if (len < 0) {
+ dev_err(dev, "malformed ocv-capacity-celsius table\n");
err = len;
goto out_put_node;
} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
- dev_err(&psy->dev, "Too many temperature values\n");
+ dev_err(dev, "Too many temperature values\n");
err = -EINVAL;
goto out_put_node;
} else if (len > 0) {
- of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
- info->ocv_temp, len);
+ u32 *tmp;
+
+ tmp = kcalloc(len, sizeof(*tmp), GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ fwnode_property_read_u32_array(battery_node,
+ "ocv-capacity-celsius",
+ tmp, len);
+ for (index = 0; index < len; index++)
+ info->ocv_temp[index] = tmp[index];
+
+ kfree(tmp);
}
for (index = 0; index < len; index++) {
- struct power_supply_battery_ocv_table *table;
char *propname;
- int i, tab_len, size;
propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index);
- list = of_get_property(battery_np, propname, &size);
- if (!list || !size) {
- dev_err(&psy->dev, "failed to get %s\n", propname);
+
+ err = get_fwnode_tuple_array(dev, battery_node, propname,
+ (struct psy_int_tuple **)
+ &info->ocv_table[index],
+ &info->ocv_table_size[index]);
+ if (err) {
kfree(propname);
- power_supply_put_battery_info(psy, info);
- err = -EINVAL;
+ power_supply_dev_put_battery_info(dev, info);
goto out_put_node;
}
-
kfree(propname);
- tab_len = size / (2 * sizeof(__be32));
- info->ocv_table_size[index] = tab_len;
-
- table = info->ocv_table[index] =
- devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
- if (!info->ocv_table[index]) {
- power_supply_put_battery_info(psy, info);
- err = -ENOMEM;
- goto out_put_node;
- }
-
- for (i = 0; i < tab_len; i++) {
- table[i].ocv = be32_to_cpu(*list);
- list++;
- table[i].capacity = be32_to_cpu(*list);
- list++;
- }
}
- list = of_get_property(battery_np, "resistance-temp-table", &len);
- if (!list || !len)
- goto out_put_node;
-
- info->resist_table_size = len / (2 * sizeof(__be32));
- resist_table = info->resist_table = devm_kcalloc(&psy->dev,
- info->resist_table_size,
- sizeof(*resist_table),
- GFP_KERNEL);
- if (!info->resist_table) {
- power_supply_put_battery_info(psy, info);
- err = -ENOMEM;
+ err = get_fwnode_tuple_array(dev, battery_node,
+ "resistance-temp-table",
+ (struct psy_int_tuple **)&info->resist_table,
+ &info->resist_table_size);
+ if (err == -ENOMEM) {
+ power_supply_dev_put_battery_info(dev, info);
goto out_put_node;
}
-
- for (index = 0; index < info->resist_table_size; index++) {
- resist_table[index].temp = be32_to_cpu(*list++);
- resist_table[index].resistance = be32_to_cpu(*list++);
- }
+ err = 0;
out_put_node:
- of_node_put(battery_np);
+ fwnode_handle_put(battery_node);
+
return err;
+
+}
+EXPORT_SYMBOL_GPL(power_supply_dev_get_battery_info);
+
+int power_supply_get_battery_info(struct power_supply *psy,
+ struct power_supply_battery_info *info)
+{
+ struct fwnode_handle *fw = NULL;
+
+ if (psy->of_node)
+ fw = of_fwnode_handle(psy->of_node);
+
+ return power_supply_dev_get_battery_info(&psy->dev, fw, info);
}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
-void power_supply_put_battery_info(struct power_supply *psy,
- struct power_supply_battery_info *info)
+void power_supply_dev_put_battery_info(struct device *dev,
+ struct power_supply_battery_info *info)
{
int i;
for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
if (info->ocv_table[i])
- devm_kfree(&psy->dev, info->ocv_table[i]);
+ devm_kfree(dev, info->ocv_table[i]);
}
if (info->resist_table)
- devm_kfree(&psy->dev, info->resist_table);
+ devm_kfree(dev, info->resist_table);
+}
+EXPORT_SYMBOL_GPL(power_supply_dev_put_battery_info);
+
+void power_supply_put_battery_info(struct power_supply *psy,
+ struct power_supply_battery_info *info)
+{
+ power_supply_dev_put_battery_info(&psy->dev, info);
}
EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index fbc07d403f41..ef7db73a5bd1 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -430,6 +430,11 @@ devm_power_supply_get_by_phandle(struct device *dev, const char *property)
{ return NULL; }
#endif /* CONFIG_OF */
+void power_supply_dev_put_battery_info(struct device *dev,
+ struct power_supply_battery_info *info);
+int power_supply_dev_get_battery_info(struct device *dev,
+ struct fwnode_handle *node,
+ struct power_supply_battery_info *info);
extern int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info *info);
extern void power_supply_put_battery_info(struct power_supply *psy,
--
2.31.1
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related [flat|nested] 34+ messages in thread
* [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction
2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
` (5 preceding siblings ...)
2021-11-16 12:27 ` [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Matti Vaittinen
@ 2021-11-16 12:28 ` Matti Vaittinen
2021-11-19 1:54 ` Linus Walleij
2021-11-16 12:29 ` [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Matti Vaittinen
2021-11-16 12:29 ` [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver Matti Vaittinen
8 siblings, 1 reply; 34+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:28 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen
Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
linux-kernel, linux-power
[-- Attachment #1: Type: text/plain, Size: 53680 bytes --]
Add generic 'simple gauge' helper for performing iterative SOC estimation
and coulomb counter correction for devices with a (drifting) coulomb
counter. This should allow few charger/fuel-gauge drivers to use generic
loop instead of implementing their own.
Charger/fuel-gauge drivers can register 'simple-gauge' which does
periodically poll the driver and:
- get battery state
- adjust coulomb counter value (to fix drifting caused for example by ADC
offset) if:
- Battery is relaxed and OCV<=>SOC table is given
- Battery is full charged
- get battery age (cycles) from driver
- get battery temperature
- do battery capacity correction
- by battery temperature
- by battery age
- by computed Vbat/OCV difference at low-battery condition if
low-limit is set and OCV table given
- by IC specific low-battery correction if provided
- compute current State Of Charge (SOC)
- do periodical calibration if IC supports that. (Many ICs do calibration
of CC by shorting the ADC pins and getting the offset).
- provide the user-space a consistent interface for getting/setting the
battery-cycle information for ICs which can't store the battery aging
information. Uses POWER_SUPPLY_PROP_CYCLE_COUNT for this.
The simple gauge provides the last computed SOC as
POWER_SUPPLY_PROP_CAPACITY to power_supply_class when requested.
Things that should/could be added but are missing from this commit:
- Support starting calibration in HW when entering to suspend. This
is useful for ICs supporting delayed calibration to mitigate CC error
during suspend - and to make periodical wake-up less critical.
- periodical wake-up for performing SOC estimation computation (RTC
integration)
Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
RFCv3:
- use devm_add_action_or_reset
- Rename to simple_gauge
- Limit access to power_supply parameters
- Introduce simple_gauge_drvdata
- no need to show simple_gauge at config menu. Users should just SELECT it.
- allow tristate
- Add blocking 'iteration run' - loop.
- updated the comment section
- Fixed clamped SOC which was not updated
- Small comment improvements
- Don't allow negative CC after computing capacity corrections
- Fix gauge looping when last client exits
---
drivers/power/supply/Kconfig | 3 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/simple-gauge.c | 1303 +++++++++++++++++++++++++++
include/linux/power/simple_gauge.h | 244 +++++
4 files changed, 1551 insertions(+)
create mode 100644 drivers/power/supply/simple-gauge.c
create mode 100644 include/linux/power/simple_gauge.h
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 5cf5bb56d2e3..24a8d030a391 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -9,6 +9,9 @@ menuconfig POWER_SUPPLY
if POWER_SUPPLY
+config POWER_SIMPLE_GAUGE
+ tristate
+
config POWER_SUPPLY_DEBUG
bool "Power supply debug"
help
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 4e55a11aab79..8c8c5f6a6492 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -5,6 +5,7 @@ power_supply-y := power_supply_core.o
power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o
power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
+obj-$(CONFIG_POWER_SIMPLE_GAUGE) += simple-gauge.o
obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
diff --git a/drivers/power/supply/simple-gauge.c b/drivers/power/supply/simple-gauge.c
new file mode 100644
index 000000000000..7bf96b4d35dd
--- /dev/null
+++ b/drivers/power/supply/simple-gauge.c
@@ -0,0 +1,1303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of some generic state-of-charge computations for devices
+ * with coulomb counter
+ *
+ * Copyright 2020 ROHM Semiconductors
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/power/simple_gauge.h>
+#include <linux/wait.h>
+
+#define SWGAUGE_TIMEOUT_JITTER 100
+/* We add 0.5% of uAh to avoid rounding error */
+#define SOC_BY_CAP(uah, round, cap) ((uah + round) * 100 / (cap))
+
+/*
+ * The idea here is to implement (typical?) periodical coulomb-counter polling
+ * and adjusting. I know few ROHM ICs which do this in out-of-tree drivers -
+ * and I think there is few drivers also in-tree doing something similar. I
+ * believe adding some SOC computation logic to core could benefit a few of the
+ * drivers.
+ *
+ * I selected the ROHM algorithm here because I know it better than others.
+ *
+ * So let's go explaining the logic pulled in here:
+ *
+ * Device drivers for (charger) ICs which contain a coulomb counter can
+ * register here and provide IC specific functions as listed in
+ * include/linux/power/simple_gauge.h struct simple_gauge_ops. Drivers can also
+ * specify time interval the IC is polled.
+ *
+ * After registration the simple_gauge does periodically poll the driver and:
+ * 1. get battery state
+ * 2. adjust coulomb counter value (to fix drifting caused for example by ADC
+ * offset) if:
+ * - Battery is relaxed and OCV<=>SOC table is given
+ * - Battery is fully charged
+ * 3. get battery age (cycles) from driver
+ * 4. get battery temperature
+ * 5. do battery capacity correction
+ * - by battery temperature
+ * - by battery age
+ * - by computed Vbat/OCV difference at low-battery condition if
+ * low-limit is set and OCV table given
+ * - by IC specific low-battery correction if provided
+ * 6. compute current State Of Charge (SOC) based on corrected capacity
+ * 7. do periodical calibration if IC supports that. (Many ICs do calibration
+ * of CC by shorting the ADC pins and getting the offset).
+ * TODO: Support starting calibration in HW when entering to suspend. This
+ * is useful for ICs supporting delayed calibration to mitigate CC error
+ * during suspend - and to make periodical wake-up less critical.
+ *
+ * The SW gauge provides the last computed SOC as POWER_SUPPLY_PROP_CAPACITY to
+ * power_supply_class when requested.
+ *
+ * Additionally the SW-gauge provides the user-space a consistent interface for
+ * getting/setting the battery-cycle information for ICs which can't store the
+ * battery aging information (like how many times battery has been charged to
+ * full) over a reset. POWER_SUPPLY_PROP_CYCLE_COUNT is used for this.
+ *
+ * TODO: Some low-power devices which may spend long times suspended may prefer
+ * periodical wake-up for performing SOC estimation/computation even at the
+ * cost of power-consumption caused by such a wake-up. So as a future
+ * improvement it would be nice to see a RTC integration which might allow the
+ * periodic wake-up. (Or is there better ideas?). This could be useful for the
+ * devices which do not support calibration when SOC is turned off and current
+ * consumption is minimal.
+ *
+ * If this is not seen as a complete waste of time - then I would like to get
+ * suggestions and opinions :) Especially for following:
+ * 1. Should this be meld-in power_supply_class? I didn't go to that route as I
+ * didn't want to obfuscate the power_supply registration with the items in
+ * the simple_gauge desc and ops. OTOH, the psy now has a pointer to sw-gauge
+ * and sw-gauge to psy - which is a clear hint that the relation of them
+ * is quite tight.
+ */
+
+static DEFINE_MUTEX(simple_gauge_lock);
+static DEFINE_MUTEX(simple_gauge_start_lock);
+static LIST_HEAD(simple_gauges);
+
+static int g_running;
+static struct task_struct k;
+
+static int simple_gauge_set_cycle(struct simple_gauge *sw, int new_cycle)
+{
+ int ret = 0, old_cycle = sw->cycle;
+
+ if (!sw->desc.allow_set_cycle && !sw->ops.set_cycle)
+ return -EINVAL;
+
+ if (sw->ops.set_cycle)
+ ret = sw->ops.set_cycle(sw, old_cycle, &new_cycle);
+
+ if (!ret)
+ sw->cycle = new_cycle;
+
+ return ret;
+}
+
+static int simple_gauge_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+ if (!sg) {
+ WARN_ON(!sg);
+ return -EINVAL;
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT && sg->desc.allow_set_cycle)
+ return simple_gauge_set_cycle(sg, val->intval);
+
+ if (sg->set_custom_property)
+ return sg->set_custom_property(sg, psp, val);
+
+ return -EOPNOTSUPP;
+}
+
+static int simple_gauge_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+ if (!sg) {
+ WARN_ON(!sg);
+ return -EINVAL;
+ }
+ /*
+ * We should ensure here is that the first iteration
+ * loop for SOC calculation must've been performed. TODO:
+ * How to ensure it?
+ *
+ * TODO: Add flags to advertice which properties can be computed by
+ * sw-gauge (with given call-backs) and only return those. Else
+ * default with driver get_property to allow the driver to compute
+ * and provide those w/o the SW-gauge.
+ *
+ * Or - we could first try calling the driver call-back and do these
+ * as a fall-back if driver returns an error?
+ */
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ spin_lock(&sg->lock);
+ val->intval = sg->soc;
+ spin_unlock(&sg->lock);
+ return 0;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ spin_lock(&sg->lock);
+ val->intval = sg->cycle;
+ spin_unlock(&sg->lock);
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ /* uAh */
+ val->intval = sg->designed_cap;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ spin_lock(&sg->lock);
+ val->intval = sg->capacity_uah;
+ spin_unlock(&sg->lock);
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ spin_lock(&sg->lock);
+ val->intval = sg->cc_uah;
+ spin_unlock(&sg->lock);
+ return 0;
+ case POWER_SUPPLY_PROP_TEMP:
+ spin_lock(&sg->lock);
+ val->intval = sg->temp;
+ spin_unlock(&sg->lock);
+ return 0;
+ default:
+ break;
+ }
+
+ if (sg->get_custom_property)
+ return sg->get_custom_property(sg, psp, val);
+
+ return -EOPNOTSUPP;
+}
+
+static void gauge_put(struct simple_gauge *sw)
+{
+ sw->refcount = 0;
+ if (!sw->refcount)
+ wake_up(&sw->wq);
+}
+
+static void gauge_get(struct simple_gauge *sw)
+{
+ sw->refcount = 1;
+}
+
+static int gauge_reserved(struct simple_gauge *sw)
+{
+ return sw->refcount;
+}
+
+static int get_dsoc_from_ocv(struct simple_gauge *sw, int *dsoc, int temp, int ocv)
+{
+ int ret;
+
+ /*
+ * The OCV tables use degree C as units (if I did not misread the
+ * code - why?) - power_supply class user-space seems to mandate
+ * the tenths of a degree - so we require this from drivers and
+ * lose accuracy here :/
+ */
+ ret = power_supply_batinfo_ocv2cap(&sw->info, ocv, temp / 10);
+ if (ret > 0) {
+ *dsoc = ret * 10;
+ ret = 0;
+ }
+
+ if (ret) {
+ if (!sw->ops.get_soc_by_ocv)
+ return ret;
+ /* For driver callbacks we use tenths of degree */
+ ret = sw->ops.get_soc_by_ocv(sw, ocv, temp, dsoc);
+ }
+ return ret;
+}
+
+static int simple_gauge_get_temp(struct simple_gauge *sw, int *temp)
+{
+ if (sw->ops.get_temp)
+ return sw->ops.get_temp(sw, temp);
+
+ return -EINVAL;
+}
+
+static int age_correct_cap(struct simple_gauge *sw, int *uah)
+{
+ int ret = 0;
+
+ /* If IC provides more complex degradation computation - use it */
+ if (sw->ops.age_correct_cap) {
+ int tmp = *uah;
+
+ ret = sw->ops.age_correct_cap(sw, sw->cycle, &tmp);
+ if (!ret) {
+ *uah = tmp;
+ return 0;
+ }
+ }
+ /* Calculate constant uAh/cycle degradation */
+ if (sw->desc.degrade_cycle_uah) {
+ int lost_cap;
+
+ lost_cap = sw->desc.degrade_cycle_uah * sw->cycle;
+ if (lost_cap > sw->designed_cap)
+ *uah = 0;
+ else
+ *uah -= sw->desc.degrade_cycle_uah * sw->cycle;
+
+ return 0;
+ }
+
+ return ret;
+}
+
+static int adjust_cc_relax(struct simple_gauge *sw, int rex_volt)
+{
+ int ret, temp, dsoc;
+ int full_uah = sw->designed_cap;
+ int uah_now;
+
+ /* get temp */
+ ret = simple_gauge_get_temp(sw, &temp);
+ if (ret)
+ return ret;
+
+ /* get ocv */
+ ret = get_dsoc_from_ocv(sw, &dsoc, temp, rex_volt);
+ if (ret)
+ return ret;
+
+ /*
+ * Typically ROHM implemented drivers have kept the value CC in PMIC
+ * corresponding to IDEAL battery capacity and then substracted the
+ * lost capacity when converting CC value to uAh. I guess this prevents
+ * CC from hitting the floor.
+ */
+ uah_now = full_uah * dsoc / 1000 + sw->soc_rounding;
+ if (uah_now > sw->designed_cap)
+ uah_now = sw->designed_cap;
+
+ return sw->ops.update_cc_uah(sw, full_uah);
+}
+
+static int get_state(struct simple_gauge *sw, int *state, int *rex_volt)
+{
+ int ret;
+ enum power_supply_property psp = POWER_SUPPLY_PROP_STATUS;
+ union power_supply_propval pstate;
+
+ *state = 0;
+
+ ret = power_supply_get_property(sw->psy, psp, &pstate);
+ if (ret)
+ return ret;
+
+ if (pstate.intval == POWER_SUPPLY_STATUS_FULL)
+ *state |= SW_GAUGE_FULL;
+ if (pstate.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+ pstate.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ *state |= SW_GAUGE_MAY_BE_LOW;
+ if (sw->desc.clamp_soc)
+ *state |= SW_GAUGE_CLAMP_SOC;
+ }
+
+ if (sw->ops.is_relaxed)
+ if (sw->ops.is_relaxed(sw, rex_volt))
+ *state |= SW_GAUGE_RELAX;
+ return ret;
+}
+
+static int adjust_cc_full(struct simple_gauge *sw)
+{
+ int ret = 0, from_full_uah = 0;
+ int full_uah = sw->designed_cap;
+
+ /*
+ * Some ICs are able to provide the uAh lost since the battery was
+ * fully charged. Decrease this from the designed capacity and set
+ * the CC value accordingly.
+ */
+ if (sw->ops.get_uah_from_full)
+ ret = sw->ops.get_uah_from_full(sw, &from_full_uah);
+
+ if (ret)
+ dev_warn(sw->dev,
+ "Failed to get capacity lost after fully charged\n");
+
+ full_uah -= from_full_uah;
+
+ /*
+ * ROHM algorithm adjusts CC here based on designed capacity and not
+ * based on age/temperature corrected capacity. This helps avoiding
+ * CC dropping below zero when we estimate aging/temperature impact
+ * badly. It also allows to keep the estimated SOC in the sw-gauge
+ * so that all IC drivers do not need to care about it - at least
+ * in theory. But most importantly - this approach is tested on the
+ * field :)
+ */
+ return sw->ops.update_cc_uah(sw, full_uah);
+}
+
+/*
+ * Some charger ICs keep count of battery charge systems but can only store
+ * one or few cycles. They may need to clear the cycle counter and update
+ * counter in SW. This function fetches the counter from HW and allows HW to
+ * clear IC counter if needed.
+ */
+static int update_cycle(struct simple_gauge *sw)
+{
+ int cycle, ret = -EINVAL;
+
+ if (sw->ops.get_cycle) {
+ /*
+ * We provide old cycle value to driver so driver does not
+ * need to cache it
+ */
+ cycle = sw->cycle;
+ ret = sw->ops.get_cycle(sw, &cycle);
+ if (ret)
+ return ret;
+ sw->cycle = cycle;
+ } else
+ sw->cycle++;
+
+ return 0;
+}
+
+static int simple_gauge_cap2ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv)
+{
+ int ret;
+
+ if (sw->ops.get_ocv_by_soc)
+ return sw->ops.get_ocv_by_soc(sw, dsoc, temp, ocv);
+
+ ret = power_supply_batinfo_dcap2ocv(&sw->info, dsoc, temp / 10);
+ if (ret > 0) {
+ *ocv = ret;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int load_based_soc_zero_adjust(struct simple_gauge *sw, int *effective_cap,
+ int cc_uah, int vsys, int temp)
+{
+ int dsoc, ocv_by_cap;
+ int ret, i;
+ int vdrop;
+ struct power_supply_battery_ocv_table *table;
+ int table_len;
+ int soc_adjust = 0;
+
+ /*
+ * Get OCV for current estimated SOC - we use unit of 0.1% for SOC
+ * (dsoc) to improve accuracy. Note - batinfo expects 1% - should we
+ * introduce new DT entry of more accurate OCV table for improved SOC
+ * => OCV where SOC was given using unit of 0.1% for improved internal
+ * calculation? User space should still only see 1% - but for "zero
+ * adjust" where we do SOC => OCV => drop-voltage => SOC correction
+ * => CC/capacity adjustment we would like to have more accurate SOC
+ * in these intermediate steps.
+ */
+ dsoc = SOC_BY_CAP(cc_uah * 10, 0, *effective_cap);
+ ret = simple_gauge_cap2ocv(sw, dsoc, temp, &ocv_by_cap);
+ if (ret) {
+ dev_err(sw->dev, "Failed to convert cap to OCV\n");
+ return ret;
+ }
+ /* Get the difference of OCV (no load) and VBAT (current load) */
+ vdrop = ocv_by_cap - vsys;
+ dev_dbg(sw->dev, "Obtained OCV: %d, vsys %d, Computed Vdrop %d\n",
+ ocv_by_cap, vsys, vdrop);
+ if (vdrop <= 0)
+ return 0;
+
+ /*
+ * We know that the SOC should be 0 at the moment when voltage with
+ * this load drops below system limit. So let's scan the OCV table
+ * and just assume the vdrop stays constant for the rest of the game.
+ * This way we can see what is the new 'zero adjusted capacity' for
+ * our battery.
+ *
+ * We don't support non DT originated OCV table here.
+ *
+ * I guess we could allow user to provide standard OCV tables in desc.
+ * Or the full batinfo for that matter. But for now we just force the
+ * drivers W/O OCV tables in DT to just provide whole low-voltage
+ * call-back.
+ */
+ table = power_supply_find_ocv2cap_table(&sw->info, temp / 10,
+ &table_len);
+ if (!table) {
+ dev_warn(sw->dev, "No OCV table found\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < table_len; i++)
+ if (table[i].ocv - vdrop <= sw->desc.system_min_voltage)
+ break;
+
+ if (!i)
+ soc_adjust = table[0].capacity;
+ else if (i && i < table_len) {
+ int j;
+ int soc_range = table[i-1].capacity - table[i].capacity;
+ int volt_range = table[i-1].ocv - table[i].ocv;
+ int v_div = volt_range/soc_range;
+
+ for (j = 0; j < soc_range; j++)
+ if (table[i].ocv + v_div * j - vdrop >=
+ sw->desc.system_min_voltage)
+ break;
+ soc_adjust = table[i].capacity + j;
+ }
+ if (soc_adjust) {
+ int new_full_cap;
+
+ /*
+ * So we know that actually we will have SOC = 0 when capacity
+ * is soc_adjust. Lets compute new battery max capacity based
+ * on this.
+ */
+ new_full_cap = *effective_cap * (100 - soc_adjust) / 100;
+
+ *effective_cap = new_full_cap;
+ }
+
+ return 0;
+}
+
+static int simple_gauge_zero_cap_adjust(struct simple_gauge *sw, int *effective_cap,
+ int *cc_uah, int vsys, int temp)
+{
+ int ret, old_eff_cap = *effective_cap;
+
+ if (sw->ops.zero_cap_adjust)
+ ret = sw->ops.zero_cap_adjust(sw, effective_cap, *cc_uah, vsys,
+ temp);
+ else
+ ret = load_based_soc_zero_adjust(sw, effective_cap, *cc_uah,
+ vsys, temp);
+ /*
+ * As we keep HW CC aligned to designed-cap - we need to
+ * also cancel this new offset from CC measured uAh
+ */
+ if (!ret)
+ *cc_uah -= old_eff_cap - *effective_cap;
+
+ return ret;
+}
+
+static int find_dcap_change(struct simple_gauge *sw, int temp, int *delta_cap)
+{
+ struct power_supply_temp_degr *dclosest = NULL, *d;
+ int i;
+
+ for (i = 0; i < sw->amount_of_temp_dgr; i++) {
+ d = &sw->temp_dgr[i];
+ if (!dclosest) {
+ dclosest = d;
+ continue;
+ }
+ if (abs(d->temp_set_point - temp) <
+ abs(dclosest->temp_set_point - temp))
+ dclosest = d;
+ }
+
+ if (!dclosest)
+ return -EINVAL;
+
+ /*
+ * Temperaure range is in tenths of degrees and degrade value is for a
+ * degree => divide by 10 after multiplication to fix the scale
+ */
+ *delta_cap = (dclosest->temp_set_point - temp) *
+ dclosest->temp_degrade_1C / 10 +
+ dclosest->degrade_at_set;
+
+ return 0;
+}
+
+static int compute_temp_correct_uah(struct simple_gauge *sw, int *cap_uah, int temp)
+{
+ int ret, uah_corr;
+
+ ret = find_dcap_change(sw, temp, &uah_corr);
+ if (ret)
+ return ret;
+
+ if (*cap_uah < -uah_corr)
+ *cap_uah = 0;
+ else
+ *cap_uah += uah_corr;
+
+ return 0;
+}
+
+static int compute_soc_by_cc(struct simple_gauge *sw, int state)
+{
+ int cc_uah, ret;
+ int current_cap_uah;
+ int temp;
+ int new_soc;
+ bool do_zero_correct, changed = false;
+
+ ret = sw->ops.get_uah(sw, &cc_uah);
+ /* The CC value should never exceed designed_cap as CC value. */
+ if (cc_uah > sw->designed_cap) {
+ cc_uah = sw->designed_cap;
+ sw->ops.update_cc_uah(sw, sw->designed_cap);
+ }
+
+ current_cap_uah = sw->designed_cap;
+
+ dev_dbg(sw->dev, "iteration started - CC %u, cap %u (SOC %u)\n",
+ cc_uah, current_cap_uah,
+ SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah));
+
+ ret = age_correct_cap(sw, ¤t_cap_uah);
+ if (ret) {
+ dev_err(sw->dev, "Age correction of battery failed\n");
+ return ret;
+ }
+
+ if (current_cap_uah == 0) {
+ dev_warn(sw->dev, "Battery EOL\n");
+ spin_lock(&sw->lock);
+ sw->capacity_uah = 0;
+ sw->soc = 0;
+ changed = true;
+ goto battery_eol_out;
+ }
+
+ /* Do battery temperature compensation */
+ ret = simple_gauge_get_temp(sw, &temp);
+ if (ret) {
+ dev_err(sw->dev, "Failed to get temperature\n");
+ return ret;
+ }
+
+ if (sw->ops.temp_correct_cap)
+ ret = sw->ops.temp_correct_cap(sw, ¤t_cap_uah, temp);
+ else if (sw->amount_of_temp_dgr)
+ ret = compute_temp_correct_uah(sw, ¤t_cap_uah, temp);
+
+ if (ret)
+ dev_warn(sw->dev,
+ "Couldn't do temperature correction to battery cap\n");
+
+ /*
+ * We keep HW CC counter aligned to ideal battery CAP - EG, when
+ * battery is full, CC is set according to ideal battery capacity.
+ * Same when we set it based on OCV. Thus - when we compute SOC we will
+ * cancel this offset by decreasing the CC uah with the lost capacity
+ */
+ cc_uah -= (sw->designed_cap - current_cap_uah);
+
+ /* Only need zero correction when discharging */
+ do_zero_correct = !!(state & SW_GAUGE_MAY_BE_LOW);
+
+ /*
+ * Allow all ICs to have own adjustment functions for low Vsys to allow
+ * them tackle potential issues in capacity estimation at near
+ * depleted battery
+ */
+ if (sw->desc.cap_adjust_volt_threshold && sw->ops.get_vsys &&
+ do_zero_correct) {
+ int vsys;
+
+ ret = sw->ops.get_vsys(sw, &vsys);
+ if (ret) {
+ dev_err(sw->dev, "Failed to get vsys\n");
+ return ret;
+ }
+
+ if (sw->desc.cap_adjust_volt_threshold >= vsys)
+ ret = simple_gauge_zero_cap_adjust(sw, ¤t_cap_uah,
+ &cc_uah, vsys, temp);
+ if (ret)
+ dev_warn(sw->dev, "Low voltage adjustment failed\n");
+ }
+
+ dev_dbg(sw->dev, "Corrected cap %u, designed-cap %u (SOC %u)\n",
+ current_cap_uah, sw->designed_cap,
+ SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah));
+
+ if (cc_uah > sw->designed_cap)
+ cc_uah = sw->designed_cap;
+
+ /*
+ * With badly behaving CC or wrong VDR values we may make the CC to go
+ * negative. Floor it to zero to avoid exhausting the battery W/O warning.
+ */
+ if (cc_uah < 0) {
+ dev_warn(sw->dev,
+ "Bad battery capacity estimate\n");
+ cc_uah = 0;
+ }
+ /* Store computed values */
+ spin_lock(&sw->lock);
+ sw->cc_uah = cc_uah;
+ sw->temp = temp;
+ sw->capacity_uah = current_cap_uah;
+ new_soc = SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah);
+
+ /*
+ * Should we only ping user-space when SOC changes more than N%?
+ * Should the N be configurable (by user-space?)
+ */
+ if (sw->soc != new_soc)
+ changed = true;
+
+ sw->soc = new_soc;
+ if (sw->clamped_soc >= 0 && state & SW_GAUGE_CLAMP_SOC) {
+ if (sw->clamped_soc < sw->soc)
+ sw->soc = sw->clamped_soc;
+ }
+ sw->clamped_soc = sw->soc;
+
+battery_eol_out:
+ spin_unlock(&sw->lock);
+ if (changed)
+ power_supply_changed(sw->psy);
+
+ return ret;
+}
+
+static void calibrate(struct simple_gauge *sw)
+{
+ if (sw->ops.calibrate)
+ sw->ops.calibrate(sw);
+}
+
+static void iterate(struct simple_gauge *sw)
+{
+ int state, ret, rex_volt;
+
+ /* Adjust battery aging information */
+ ret = update_cycle(sw);
+ if (ret) {
+ dev_err(sw->dev, "Failed to update battery cycle\n");
+ return;
+ }
+
+ ret = get_state(sw, &state, &rex_volt);
+ if (ret) {
+ dev_err(sw->dev, "Failed to get state\n");
+ return;
+ }
+
+ /* Setting CC not possible? Omit CC adjustment */
+ if (sw->ops.update_cc_uah) {
+ if (state & SW_GAUGE_FULL) {
+ ret = adjust_cc_full(sw);
+ if (ret)
+ dev_err(sw->dev, "Failed to do FULL adjust\n");
+ }
+ if (state & SW_GAUGE_RELAX) {
+ ret = adjust_cc_relax(sw, rex_volt);
+ if (ret)
+ dev_err(sw->dev, "Failed to do RELAX adjust\n");
+ }
+ }
+
+ ret = compute_soc_by_cc(sw, state);
+ if (ret)
+ dev_err(sw->dev, "Failed to compute SOC for gauge\n");
+}
+
+static bool should_calibrate(struct simple_gauge *sw, u64 time)
+{
+ if (sw->desc.calibrate_interval &&
+ sw->next_cal <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER)) {
+ sw->next_cal = time +
+ msecs_to_jiffies(sw->desc.calibrate_interval);
+ return true;
+ }
+
+ return false;
+
+}
+
+static bool should_compute(struct simple_gauge *sw, u64 time)
+{
+ if (sw->next_iter <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER) ||
+ sw->force_run) {
+ sw->force_run = 0;
+ sw->next_iter = time +
+ msecs_to_jiffies(sw->desc.poll_interval);
+ return true;
+ }
+
+ return false;
+}
+
+static void adjust_next_tmo(struct simple_gauge *sw, u64 *timeout, u64 now)
+{
+ u64 t = (sw->desc.calibrate_interval && sw->next_cal < sw->next_iter) ?
+ sw->next_cal : sw->next_iter;
+
+ if (!*timeout || t - now < *timeout)
+ *timeout = t - now;
+
+ if (*timeout < msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER))
+ *timeout = msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER);
+}
+
+static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_thread_wait);
+static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_forced_wait);
+
+static int simple_gauge_forced_run;
+
+/**
+ * simple_gauge_run - force running the computation loop for gauge
+ *
+ * Drivers utilizing simple_gauge can trigger running the SOC computation loop even
+ * prior the time-out occurs. This is usable for drivers with longish period
+ * but which may get interrupts form device when some condition changes. Note,
+ * this function schedules the iteration but does not block.
+ *
+ * @sw: gauge fow which the computation should be ran.
+ */
+void simple_gauge_run(struct simple_gauge *sw)
+{
+ sw->force_run = 1;
+ simple_gauge_forced_run = 1;
+ barrier();
+ wake_up(&simple_gauge_thread_wait);
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run);
+
+static unsigned int g_iteration;
+
+static unsigned int simple_gauge_run_locked(struct simple_gauge *sg)
+{
+ unsigned int ctr;
+
+ /* Wait for any ongoing iteration */
+ mutex_lock(&simple_gauge_lock);
+ ctr = g_iteration;
+ simple_gauge_run(sg);
+ mutex_unlock(&simple_gauge_lock);
+
+ return ctr;
+}
+
+/**
+ * simple_gauge_run_blocking_timeout - Run gauge loop and block until ran
+ *
+ * Trigger simple-gauge to run. Wait until simple-gauge has been ran or timeout
+ * occurs.
+ *
+ * @sg: Pointer to gauge
+ * @timeout_ms: Timeout in milliseconds.
+ *
+ * Returns: 0 if gauge loop was ran. -EAGAIN if timeout occurred and
+ * -ERESTARTSYS if call was interrupted.
+ */
+int simple_gauge_run_blocking_timeout(struct simple_gauge *sg,
+ unsigned int timeout_ms)
+{
+ unsigned int ctr;
+ int ret;
+
+ ctr = simple_gauge_run_locked(sg);
+ ret = wait_event_interruptible_timeout(simple_gauge_forced_wait,
+ g_iteration > ctr,
+ timeout_ms);
+ if (ret == 1)
+ ret = 0;
+ else if (!ret)
+ ret = -EAGAIN;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run_blocking_timeout);
+
+/**
+ * simple_gauge_run_blocking - Run gauge loop and block until ran
+ *
+ * Trigger simple-gauge to run. Wait until simple-gauge has been ran.
+ *
+ * @sg: Pointer to gauge
+ *
+ * Returns: 0 on success. -ERESTARTSYS if call was interrupted.
+ */
+int simple_gauge_run_blocking(struct simple_gauge *sg)
+{
+ unsigned int ctr;
+ int ret;
+
+ ctr = simple_gauge_run_locked(sg);
+ ret = wait_event_interruptible(simple_gauge_forced_wait,
+ g_iteration > ctr);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run_blocking);
+
+static int gauge_thread(void *data)
+{
+
+ for (;;) {
+ u64 timeout = 0;
+ struct simple_gauge *sw;
+ u64 now = get_jiffies_64();
+
+ if (kthread_should_stop()) {
+ g_running = 0;
+ pr_info("gauge thread stopping...\n");
+ /*
+ * Ensure the g_running is visible to all. OTOH - if
+ * thread stopping is not supported we can drop this
+ * clearing.
+ */
+ smp_wmb();
+ break;
+ }
+
+ simple_gauge_forced_run = 0;
+
+ mutex_lock(&simple_gauge_lock);
+ list_for_each_entry(sw, &simple_gauges, node) {
+ gauge_get(sw);
+ if (should_compute(sw, now))
+ iterate(sw);
+ if (should_calibrate(sw, now))
+ calibrate(sw);
+ adjust_next_tmo(sw, &timeout, now);
+ gauge_put(sw);
+ }
+ /*
+ * Increase iteration counter and wake up the waiters who have
+ * requested the blocking forced run. NOTE: We must increase
+ * iteration here inside the locking to avoid race.
+ */
+ g_iteration++;
+ mutex_unlock(&simple_gauge_lock);
+ wake_up(&simple_gauge_forced_wait);
+
+ /*
+ * If the last gauge exited we fall to sleep in order to not
+ * go lopoping with zero timeout. New client registration will
+ * wake us up.
+ */
+ if (!timeout && list_empty(&simple_gauges)) {
+ pr_debug("No clients: going to sleep\n");
+ wait_event_interruptible(simple_gauge_thread_wait,
+ simple_gauge_forced_run);
+ } else {
+ pr_debug("sleeping %u msec\n",
+ jiffies_to_msecs(timeout));
+ wait_event_interruptible_timeout(simple_gauge_thread_wait,
+ simple_gauge_forced_run, timeout);
+ }
+ }
+
+ return 0;
+}
+
+static int start_gauge_thread(struct task_struct *k)
+{
+ int ret = 0;
+
+ /* Ensure the running state is updated. Is this needed? How likely it is
+ * this would not be updated? Only reason why we have this first check
+ * is to avoid unnecessarily getting the lock when the thread is started
+ * - and it should always be except for the first caller. But does this
+ * memory barrier actually make this almost as heavy as getting the lock
+ * every time? Maybe we should just drop the barrier and let us hit the
+ * lock if g_running is in cache or some such (lots of hand waving to
+ * make it look like I knew what I was talking about :]).
+ */
+ smp_rmb();
+ if (g_running)
+ return 0;
+
+ /*
+ * Would this be cleaner if we used schedule_delayed_work? rather than
+ * kthread? What I like is only one thread for sw-gauge, with
+ * not-so-high priority. What is the simplest way to achieve it?
+ */
+ mutex_lock(&simple_gauge_start_lock);
+ if (!g_running) {
+ k = kthread_run(gauge_thread, NULL, "sw-gauge");
+ if (IS_ERR(k))
+ ret = PTR_ERR(k);
+ else
+ g_running = 1;
+ }
+ mutex_unlock(&simple_gauge_start_lock);
+
+ return ret;
+}
+
+/*
+ * I think this is unnecessary. If someone registers SW gauge to the system
+ * then we can probably leave this running even if the gauge was temporarily
+ * removed. So let's consider removing this and thus simplifying the design.
+ *
+ * Perhaps even always launch the thread if SW-gauge is configured in?
+ */
+void stop_gauge_thread(struct task_struct *k);
+void stop_gauge_thread(struct task_struct *k)
+{
+ kthread_stop(k);
+}
+
+static bool is_needed_ops_given(struct simple_gauge_ops *ops)
+{
+ return (ops->get_uah && ops->get_temp && ops->update_cc_uah);
+}
+
+static int simple_gauge_set_ops(struct simple_gauge *sw, struct simple_gauge_ops *ops)
+{
+ if (!is_needed_ops_given(ops))
+ return -EINVAL;
+
+ sw->ops = *ops;
+
+ return 0;
+}
+
+static int simple_gauge_is_writable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+ if (!sg) {
+ WARN_ON(!sg);
+ return -EINVAL;
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT)
+ return sg->desc.allow_set_cycle;
+
+ if (sg->custom_is_writable)
+ return sg->custom_is_writable(sg, psp);
+
+ return 0;
+}
+
+static int sgauge_config_check(struct device *dev, struct simple_gauge_psy *pcfg)
+{
+ const char *errstr = NULL;
+
+ if (!pcfg) {
+ errstr = "Missing config";
+ } else {
+ if (!pcfg->psy_name)
+ errstr = "No power supply name";
+ if (pcfg->num_additional_props) {
+ if (!pcfg->get_custom_property)
+ errstr = "property reader required";
+ }
+ if (!pcfg->is_writable) {
+ if (pcfg->set_custom_property)
+ errstr =
+ "set_custom_property() but no is_writable()";
+ }
+ }
+ if (!errstr)
+ return 0;
+
+ dev_err(dev, "%s\n", errstr);
+ return -EINVAL;
+}
+static int simple_gauge_set_props(struct simple_gauge *new,
+ struct power_supply_desc *pd,
+ struct simple_gauge_psy *pcfg)
+{
+ int i, j;
+
+ new->properties = kzalloc(sizeof(*pcfg->additional_props) *
+ pcfg->num_additional_props + SIMPLE_GAUGE_PROP_SIZE,
+ GFP_KERNEL);
+ if (!new->properties)
+ return -ENOMEM;
+
+ for (i = 0; i < NUM_SIMPLE_GAUGE_PROPS; i++)
+ new->properties[i] = SIMPLE_GAUGE_PROPS[i];
+
+ for (j = 0; j < pcfg->num_additional_props; j++, i++)
+ new->properties[i] = pcfg->additional_props[j];
+
+ pd->properties = new->properties;
+ pd->num_properties = i;
+ new->get_custom_property = pcfg->get_custom_property;
+ new->set_custom_property = pcfg->set_custom_property;
+
+ return 0;
+}
+
+void *simple_gauge_get_drvdata(struct simple_gauge *sg)
+{
+ return sg->desc.drv_data;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_get_drvdata);
+
+/**
+ * psy_register_simple_gauge - register driver to simple_gauge
+ *
+ * @parent: Parent device for power-supply class device.
+ * @psycfg: Confiurations for power-supply class.
+ * @ops: simple_gauge specific operations.
+ * @desc: simple_gauge configuration data.
+ *
+ * Return: pointer to simple_gauge on success, an ERR_PTR on failure.
+ *
+ * A power-supply driver for a device with drifting coulomb counter (CC) can
+ * register for periodical polling/CC correction. CC correction is done when
+ * battery is reported to be FULL or relaxed. For FULL battery the CC is set
+ * based on designed capacity and for relaxed battery CC is set based on open
+ * circuit voltage. The simple_gauge takes care of registering a power-supply class
+ * and reporting a few power-supply properties to user-space. See
+ * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on
+ * provided temperature/cycle degradation values and/or system voltage limit.
+ */
+struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent,
+ struct simple_gauge_psy *pcfg,
+ struct simple_gauge_ops *ops,
+ struct simple_gauge_desc *desc)
+{
+ struct power_supply_desc *pd;
+ struct power_supply_config pg = { 0 };
+ int ret;
+ struct simple_gauge *new;
+
+ if (!parent) {
+ pr_err("no parent\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!desc->poll_interval) {
+ dev_err(parent, "interval missing\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (sgauge_config_check(parent, pcfg))
+ return ERR_PTR(-EINVAL);
+
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return ERR_PTR(-ENOMEM);
+
+ new = kzalloc(sizeof(*new), GFP_KERNEL);
+ if (!new) {
+ ret = -ENOMEM;
+ goto free_out;
+ }
+ pd->name = pcfg->psy_name;
+ pd->type = POWER_SUPPLY_TYPE_BATTERY;
+
+ pg.drv_data = new;
+ pg.of_node = pcfg->of_node;
+ pg.attr_grp = pcfg->attr_grp;
+
+ ret = simple_gauge_set_props(new, pd, pcfg);
+ if (ret) {
+ ret = -ENOMEM;
+ goto free_out;
+ }
+ new->dev = parent;
+ new->custom_is_writable = pcfg->is_writable;
+ /* We don't want to clamp SOC before it is initialized */
+ new->clamped_soc = -1;
+
+ init_waitqueue_head(&new->wq);
+
+ ret = simple_gauge_set_ops(new, ops);
+ if (ret) {
+ dev_err(new->dev, "bad ops\n");
+ goto free_out;
+ }
+ new->desc = *desc;
+
+ spin_lock_init(&new->lock);
+ pd->get_property = simple_gauge_get_property;
+ pd->set_property = simple_gauge_set_property;
+ if (pcfg->is_writable || desc->allow_set_cycle)
+ pd->property_is_writeable = simple_gauge_is_writable;
+
+ /* Do we need power_supply_register_ws? */
+ /*
+ * We should not return SOC to user-space before the first
+ * estimation iteration is ran. How should we sync this? Should we
+ * actually start the gauge thread and try getting SOC prior
+ * registering to psy class? That would require us to do the
+ * battery-info reading here. Or should we have some way of delaying
+ * sysfs creation from psy-class until first iteration is done? Kind
+ * of two-step power-supply class registration? Again, I am open to
+ * all suggestions!
+ *
+ * I don't know how this should be integrated to psy class. Should
+ * this sit between psy-class and driver? Should this be part of
+ * psy-class or ?? If this was meld in psy-class registration and
+ * the psy class registration was just given a new parameter for fuel
+ * gauge desc - then the synchronization between 1st iteration and
+ * sysfs creation might get more natural.
+ */
+ new->psy = power_supply_register(parent, pd, &pg);
+ if (IS_ERR(new->psy)) {
+ dev_err(new->dev, "power supply registration failed\n");
+ ret = PTR_ERR(new->psy);
+ goto free_out;
+ }
+
+ ret = power_supply_get_battery_info(new->psy, &new->info);
+ if (ret && !new->ops.get_soc_by_ocv) {
+ dev_err(new->dev, "No OCV => SoC conversion\n");
+ goto info_out;
+ }
+ if (!ret)
+ new->batinfo_got = true;
+
+ if (new->info.temp_dgrd_values) {
+ new->amount_of_temp_dgr = new->info.temp_dgrd_values;
+ new->temp_dgr = new->info.temp_dgrd;
+ } else {
+ new->amount_of_temp_dgr = new->desc.amount_of_temp_dgr;
+ new->temp_dgr = new->desc.temp_dgr;
+ }
+
+ if (desc->designed_cap) {
+ new->designed_cap = desc->designed_cap;
+ } else if (ret || !new->info.charge_full_design_uah) {
+ dev_err(new->dev, "Unknown battery capacity\n");
+ goto info_out;
+ } else {
+ new->designed_cap = new->info.charge_full_design_uah;
+ }
+ /* We add 0.5 % to soc uah in order to avoid flooring */
+ new->soc_rounding = new->designed_cap / 200;
+ mutex_lock(&simple_gauge_lock);
+ list_add(&new->node, &simple_gauges);
+ mutex_unlock(&simple_gauge_lock);
+ ret = start_gauge_thread(&k);
+ if (ret) {
+ /*
+ * This error is not related to underlying device but to the
+ * simple_gauge itself. Thus don't print error using the parent
+ * device
+ */
+ pr_err("Failed to start fuel-gauge thread\n");
+ goto info_out;
+ }
+ dev_dbg(new->dev, "YaY! SW-gauge registered\n");
+
+ simple_gauge_run_blocking(new);
+
+ return new;
+
+info_out:
+ if (new->batinfo_got)
+ power_supply_put_battery_info(new->psy, &new->info);
+
+ power_supply_unregister(new->psy);
+free_out:
+ if (new) {
+ kfree(new->properties);
+ kfree(new);
+ }
+ kfree(pd);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(psy_register_simple_gauge);
+
+/**
+ * psy_remove_simple_gauge - deregister driver from simple_gauge
+ *
+ * @sw: gauge driver to be deregistered.
+ */
+void psy_remove_simple_gauge(struct simple_gauge *sw)
+{
+ const struct power_supply_desc *desc;
+
+ mutex_lock(&simple_gauge_lock);
+ list_del(&sw->node);
+ mutex_unlock(&simple_gauge_lock);
+
+ wait_event(sw->wq, !gauge_reserved(sw));
+
+ if (sw->batinfo_got)
+ power_supply_put_battery_info(sw->psy, &sw->info);
+
+ desc = sw->psy->desc;
+ power_supply_unregister(sw->psy);
+
+ kfree(desc);
+ kfree(sw->properties);
+ kfree(sw);
+}
+EXPORT_SYMBOL_GPL(psy_remove_simple_gauge);
+
+static void devm_simple_gauge_release(void *res)
+{
+ psy_remove_simple_gauge(res);
+}
+
+/**
+ * devm_psy_register_simple_gauge - managed register driver to simple_gauge
+ *
+ * @parent: Parent device for power-supply class device. Swgauge's lifetime
+ * is also bound to this device.
+ * @psycfg: Confiurations for power-supply class.
+ * @ops: simple_gauge specific operations.
+ * @desc: simple_gauge configuration data.
+ *
+ * Return: pointer to simple_gauge on success, an ERR_PTR on failure.
+ *
+ * A power-supply driver for a device with drifting coulomb counter (CC) can
+ * register for periodical polling/CC correction. CC correction is done when
+ * battery is reported to be FULL or relaxed. For FULL battery the CC is set
+ * based on designed capacity and for relaxed battery CC is set based on open
+ * circuit voltage. The simple_gauge takes care of registering a power-supply class
+ * and reporting a few power-supply properties to user-space. See
+ * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on
+ * provided temperature/cycle degradation values and/or system voltage limit.
+ */
+struct simple_gauge *__must_check
+devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg,
+ struct simple_gauge_ops *ops, struct simple_gauge_desc *desc)
+{
+ struct simple_gauge *sw;
+ int ret;
+
+ sw = psy_register_simple_gauge(parent, psycfg, ops, desc);
+ if (IS_ERR(sw))
+ return sw;
+
+ ret = devm_add_action_or_reset(parent, devm_simple_gauge_release, sw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return sw;
+}
+EXPORT_SYMBOL_GPL(devm_psy_register_simple_gauge);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("generic fuel-gauge on coulomb counter");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
diff --git a/include/linux/power/simple_gauge.h b/include/linux/power/simple_gauge.h
new file mode 100644
index 000000000000..cd085375ddec
--- /dev/null
+++ b/include/linux/power/simple_gauge.h
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020 ROHM Semiconductors */
+
+#ifndef POWER_SW_GAUGE_H
+#define POWER_SW_GAUGE_H
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define SW_GAUGE_FULL BIT(0)
+#define SW_GAUGE_RELAX BIT(1)
+#define SW_GAUGE_MAY_BE_LOW BIT(2)
+#define SW_GAUGE_CLAMP_SOC BIT(3)
+
+/* Power supply properties handled by simple_gauge */
+static const enum power_supply_property SIMPLE_GAUGE_PROPS[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP
+};
+#define SIMPLE_GAUGE_PROP_SIZE (sizeof(SIMPLE_GAUGE_PROPS))
+#define NUM_SIMPLE_GAUGE_PROPS (ARRAY_SIZE(SIMPLE_GAUGE_PROPS))
+
+struct simple_gauge;
+
+/**
+ * struct simple_gauge_ops - fuel-gauge operations
+ *
+ * @is_relaxed: return true if battery is at relaxed state. Update
+ * rex_volt to contain measured relaxed battery voltage.
+ * @get_temp: return the battery temperature in tenths of a degree C.
+ * @get_uah_from_full: some chargers can provide CC value change since battery
+ * was last charged full. This value can be used by
+ * sw-gauge when correcting CC based on battery full
+ * status. This function should return charge lost since
+ * battery was last load full. Units in uAh.
+ * @get_uah: return current charge as measured by coulomb counter in
+ * uAh
+ * @update_cc_uah: Update CC by given charge in uAh
+ * @get_cycle: get battery cycle for age compensation
+ * @set_cycle: some batteries/chargers rely on user-space to store the
+ * cycle infomration over reset. Those drivers can
+ * implement the set_cycle callback which user-space can
+ * use to set the stored battery cycle after reset.
+ * @get_sys: get the current system voltage in uV. Used for
+ * IC specific low-voltage SOC correction.
+ * @get_soc_by_ocv: setups which do not store the OCV/SOC information in
+ * standard battery_info can implement this function to
+ * compute SOC based on OCV. SOC should be returned as
+ * units of 0.1%
+ * @get_ocv_by_soc: setups which do not store the OCV/SOC information in
+ * standard battery_info can implement this function to
+ * compute OCV based on SOC. NOTE: Soc is provided to
+ * the function in units of 0.1% to improve accuracy.
+ * @age_correct_cap: batteries/devices with more complicated aging
+ * correction than constant uAh times battery cycles
+ * can implement this to adjust capacity based on battery
+ * cycles. For constant aging use degrade_cycle_uah
+ * in desc.
+ * @temp_correct_cap: batteries/devices with more complicated temperature
+ * correction than ranges of temperatures with constant
+ * change uah/degree C can implement this to adjust
+ * capacity based on battery temperature.
+ * For temperature ranges with constant change uAh/degree
+ * use temp_dgr and amount_of_temp_dgr at desc.
+ * @calibrate: many devices implement coulomb counter calibration
+ * (for example by measuring ADC offset pins shorted).
+ * Such devices can implement this function for periodical
+ * calibration.
+ * @suspend_calibrate: Many small capacity battery devices or devices which
+ * spend long time MCU suspended can benefit from
+ * starting the calibration when entering to suspend. Such
+ * devices can implement this callback to initiaite
+ * calibration when entering to suspend
+ * @zero_cap_adjust: IC specific SOC estimation adjustment to be performed
+ * when battery is approaching empty.
+ */
+struct simple_gauge_ops {
+ /*
+ * Get battery relax - could probably also use PSY class state if
+ * it was extended with some properties like BATTERY_RELAXED to know
+ * if OCV can be used.
+ *
+ * Currently meaningfull states are charging/discharging/full/relaxed
+ * Full so we can correct battery capacity and/or CC
+ * Relax so we know we can use OCV
+ */
+ bool (*is_relaxed)(struct simple_gauge *gauge, int *rex_volt);
+ int (*get_temp)(struct simple_gauge *gauge, int *temp);
+ int (*get_uah_from_full)(struct simple_gauge *gauge, int *uah);
+ int (*get_uah)(struct simple_gauge *gauge, int *uah);
+ int (*update_cc_uah)(struct simple_gauge *gauge, int bcap);
+ int (*get_cycle)(struct simple_gauge *gauge, int *cycle);
+ int (*set_cycle)(struct simple_gauge *gauge, int old, int *new_cycle);
+ int (*get_vsys)(struct simple_gauge *gauge, int *uv);
+ int (*get_soc_by_ocv)(struct simple_gauge *sw, int ocv, int temp, int *soc);
+ int (*get_ocv_by_soc)(struct simple_gauge *sw, int soc, int temp, int *ocv);
+ int (*age_correct_cap)(struct simple_gauge *gauge, int cycle, int *cap);
+ int (*temp_correct_cap)(struct simple_gauge *gauge, int *cap, int temp);
+ int (*calibrate)(struct simple_gauge *sw);
+ int (*suspend_calibrate)(struct simple_gauge *sw, bool start);
+ int (*zero_cap_adjust)(struct simple_gauge *sw, int *effective_cap,
+ int cc_uah, int vbat, int temp);
+};
+
+/**
+ * struct simple_gauge_desc - fuel gauge description
+ *
+ * The fuel gauges which benefit from generic computations (typically devices
+ * with coulomb counter. OCV - SOC table and iterative polling / error
+ * correction) provided by the simple_gauge framework must be described by the
+ * simple_gauge_desc prior registration to the simple_gauge framework.
+ *
+ * @name: Identifying name for gauge (Is this needed?)
+ * @degrade_cycle_uah: constant lost capacity / battery cycle in uAh.
+ * @amount_of_temp_dgr: amount of temperature ranges provided in temp_dgr
+ * @temp_dgr: ranges of constant lost capacity / temperature degree
+ * in uAh. Ranges should be sorted in asecnding order by
+ * temperature_floor.
+ * @poll_interval: time interval in mS at which this fuel gauge iteration
+ * loop for volage polling and coulomb counter corrections
+ * should be ran.
+ * @calibrate_interval: time interval in mS at which this IC should be
+ * calibrated.
+ * @designed_cap: designed battery capacity in uAh. Can be given here if
+ * not available via batinfo.
+ * @allow_set_cycle: Allow userspace to set cached battery cycle. If no HW
+ * access is required when new battery cycle value is set
+ * the driver can omit set_cycle callback and just set
+ * this to true.
+ * @clamp_soc: Set true tonot allow computed SOC to increase if state
+ * is discharging.
+ * @cap_adjust_volt_threshold: some systems want to apply extra computation
+ * to estimate battery capacity when voltage gets close
+ * to system limit in order to avoid shut-down for as long
+ * as possible. Such ICs can set this limit and optionally
+ * implement zero_cap_adjust callback.
+ * @system_min_voltage: ICs using the cap_adjust_volt_threshold and no
+ * zero_cap_adjust call-back should set this voltage
+ * to Vsys which correspond empty battery situation.
+ */
+struct simple_gauge_desc {
+ int degrade_cycle_uah;
+ int amount_of_temp_dgr;
+ struct power_supply_temp_degr *temp_dgr;
+ int poll_interval;
+ int calibrate_interval;
+ int designed_cap; /* This is also looked from batinfo (DT node) */
+ int cap_adjust_volt_threshold;
+ int system_min_voltage;
+ bool allow_set_cycle;
+ bool clamp_soc;
+ void *drv_data;
+};
+
+/**
+ * struct simple_gauge_psy - power supply configuration
+ *
+ * configuration being further passed to power-supply registration.
+ */
+struct simple_gauge_psy {
+ const char *psy_name;
+ struct device_node *of_node;
+ /* Device specific sysfs attributes, delivered to power_supply */
+ const struct attribute_group **attr_grp;
+
+ enum power_supply_property *additional_props;
+ int num_additional_props;
+
+ int (*is_writable)(struct simple_gauge *gauge,
+ enum power_supply_property psp);
+ int (*get_custom_property)(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+ int (*set_custom_property)(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ const union power_supply_propval *val);
+};
+
+/**
+ * struct simple_gauge - simple_gauge runtime data
+ *
+ * Internal to sw-gauge. Should not be directly accessed/modified by drivers
+ */
+struct simple_gauge {
+ struct device *dev;
+ int designed_cap; /* This should be available for drivers */
+ struct simple_gauge_desc desc;
+ int cycle;
+ u64 next_iter; /* Time of next iteration in jiffies64 */
+ u64 next_cal; /* Time of next calibration in jiffies64 */
+ int force_run;
+ int refcount;
+ struct power_supply *psy;
+ enum power_supply_property *properties;
+
+ int (*get_custom_property)(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+ int (*set_custom_property)(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ const union power_supply_propval *val);
+ int (*custom_is_writable)(struct simple_gauge *gauge,
+ enum power_supply_property psp);
+ struct power_supply_battery_info info;
+ struct simple_gauge_ops ops;
+ struct list_head node;
+ int amount_of_temp_dgr;
+ struct power_supply_temp_degr *temp_dgr;
+ spinlock_t lock;
+ bool batinfo_got;
+ wait_queue_head_t wq;
+ int soc_rounding;
+ int clamped_soc;
+ /* Cached values from prev iteration */
+ int soc; /* SOC computed at previous iteration */
+ int capacity_uah; /* CAP computed at previous iteration (uAh) */
+ int cc_uah; /* uAh reported by CC at previous iteration */
+ int temp; /* Temperature at previous iteration */
+};
+
+struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent,
+ struct simple_gauge_psy *psycfg,
+ struct simple_gauge_ops *ops,
+ struct simple_gauge_desc *desc);
+void psy_remove_simple_gauge(struct simple_gauge *sw);
+
+struct simple_gauge *__must_check
+devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg,
+ struct simple_gauge_ops *ops,
+ struct simple_gauge_desc *desc);
+void simple_gauge_run(struct simple_gauge *sw);
+int simple_gauge_run_blocking_timeout(struct simple_gauge *sg,
+ unsigned int timeout_ms);
+int simple_gauge_run_blocking(struct simple_gauge *sg);
+void *simple_gauge_get_drvdata(struct simple_gauge *sg);
+
+#endif /* POWER_SW_GAUGE_H */
--
2.31.1
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related [flat|nested] 34+ messages in thread
* [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver
2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
` (7 preceding siblings ...)
2021-11-16 12:29 ` [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Matti Vaittinen
@ 2021-11-16 12:29 ` Matti Vaittinen
2021-11-17 2:06 ` kernel test robot
` (2 more replies)
8 siblings, 3 replies; 34+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:29 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen
Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
linux-kernel, linux-power
[-- Attachment #1: Type: text/plain, Size: 84786 bytes --]
Add charger driver for ROHM BD718(15/27/28/78) PMIC charger block.
Driver utilizes the simple-gauge for battery status / current polling,
CC correction and SOC estimation.
A version of driver which does not utilize simple-gauge can be seen
in ROHM venor specific Linux tree - comparison can visualize how
simple-gauge can simplify IC specific drivers.
https://github.com/RohmSemiconductor/Linux-Kernel-PMIC-Drivers/blob/stable-v5.4.6/drivers/power/supply/bd71827-power.c
Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
Changes for RFCv3:
- adapt to changed swgauge name (simple_gauge)
- adapt to simple_gauge parameter changes
- Initial BD71815 support
- Use drvdata properly.
- Sort includes
- Prepare to provide dcin_collapse voltage from DT
- clean unused defines
- use OCV tables from batinfo if module params not given
- do not directly call bd71827_voltage_to_capacity from calibration
but use provided operation.
- Mask the power-state from relax-condition on BD71815 as is done by
the ROHM driver. REX state is used to do OCV => SOC conversion
when battery is relaxed even if REX_CC was not used.
- Clarify that we require either the module params or DT values for
battery. Fail probe if parameters are not given.
- Utilize degrade_cycle_uah aging degradation.
- Get battery max and min values either as module parameters or from
static battery node at DT.
- Allow giving the zero correction threshold as a module param or
compute it as 10% of "remaining battery voltage" based on max and
min voltages given via DT.
- Add proper MODULE_ALIAS
- Implement VDR table reading from DT
- Do not require fixed amount of battery parameters
- Fix Coulomb Counter to uAh conversion
- Fix endianess related warnings
- clean-up comment
- Avoid dividing by zero at VDR computation
- Use the fwnode API instead of of_* API
. don't assume 32bit int
- Fix IC type prints
- Fix the current sense resistor DT property *-ohm => *-ohms
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/bd71827-power.c | 2473 ++++++++++++++++++++++++++
include/linux/mfd/rohm-bd71827.h | 295 +++
4 files changed, 2780 insertions(+)
create mode 100644 drivers/power/supply/bd71827-power.c
create mode 100644 include/linux/mfd/rohm-bd71827.h
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 24a8d030a391..384afd53fe46 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -791,6 +791,17 @@ config CHARGER_UCS1002
Say Y to enable support for Microchip UCS1002 Programmable
USB Port Power Controller with Charger Emulation.
+config CHARGER_BD71828
+ tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC"
+ depends on MFD_ROHM_BD71828
+ select POWER_SIMPLE_GAUGE
+ help
+ Say Y here to enable support for charger, battery and fuel gauge
+ in ROHM BD71815, BD71817, BD71827, ROHM BD71828 power management
+ ICs. This driver gets various bits of information about battery
+ and charger states and also implements fuel gauge based on
+ coulomb counters on PMIC.
+
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 8c8c5f6a6492..e26f6ae598ad 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD71828) += bd71827-power.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
diff --git a/drivers/power/supply/bd71827-power.c b/drivers/power/supply/bd71827-power.c
new file mode 100644
index 000000000000..6d2a9bc6f3dd
--- /dev/null
+++ b/drivers/power/supply/bd71827-power.c
@@ -0,0 +1,2473 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * bd71827-power.c
+ * @file ROHM BD71815, BD71827, BD71828 and BD71878 Charger driver
+ *
+ * Copyright 2021.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/linear_range.h>
+#include <linux/mfd/rohm-bd71815.h>
+#include <linux/mfd/rohm-bd71827.h>
+#include <linux/mfd/rohm-bd71828.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power/simple_gauge.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y))
+#define uAMP_TO_mAMP(ma) ((ma) / 1000)
+
+#define LINEAR_INTERPOLATE(y_hi, y_lo, x_hi, x_lo, x) \
+ ((y_lo) + ((x) - (x_lo)) * ((y_hi) - (y_lo)) / ((x_hi) - (x_lo)))
+
+#define CAP2DSOC(cap, full_cap) ((cap) * 1000 / (full_cap))
+
+/* common defines */
+#define BD7182x_MASK_VBAT_U 0x1f
+#define BD7182x_MASK_VDCIN_U 0x0f
+#define BD7182x_MASK_IBAT_U 0x3f
+#define BD7182x_MASK_CURDIR_DISCHG 0x80
+#define BD7182x_MASK_CC_CCNTD_HI 0x0FFF
+#define BD7182x_MASK_CC_CCNTD 0x0FFFFFFF
+#define BD7182x_MASK_CHG_STATE 0x7f
+#define BD7182x_MASK_CC_FULL_CLR 0x10
+#define BD7182x_MASK_BAT_TEMP 0x07
+#define BD7182x_MASK_DCIN_DET BIT(0)
+#define BD7182x_MASK_CONF_PON BIT(0)
+#define BD71815_MASK_CONF_XSTB BIT(1)
+#define BD7182x_MASK_BAT_STAT 0x3f
+#define BD7182x_MASK_DCIN_STAT 0x07
+
+#define BD7182x_MASK_CCNTRST 0x80
+#define BD7182x_MASK_CCNTENB 0x40
+#define BD7182x_MASK_CCCALIB 0x20
+#define BD7182x_MASK_WDT_AUTO 0x40
+#define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01
+#define BD7182x_MASK_CHG_EN 0x01
+
+#define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36
+
+static const struct linear_range dcin_collapse = {
+ .min = 0,
+ .min_sel = 0,
+ .max_sel = 0xff,
+ /* Anti-collapse voltage threshold 0.0V to 20.4V range, 80 mV steps */
+ .step = 80000,
+};
+
+/* Measured min and max value clear bits */
+#define BD718XX_MASK_VSYS_MIN_AVG_CLR 0x10
+
+#define JITTER_DEFAULT 3000
+#define MAX_CURRENT_DEFAULT 890000 /* uA */
+#define AC_NAME "bd71827_ac"
+#define BAT_NAME "bd71827_bat"
+
+/*
+ * VBAT Low voltage detection Threshold
+ * 0x00D4*16mV = 212*0.016 = 3.392v
+ */
+#define VBAT_LOW_TH 0x00D4
+
+#define THR_RELAX_CURRENT_DEFAULT 5 /* mA */
+#define THR_RELAX_TIME_DEFAULT (60 * 60) /* sec. */
+
+#define DGRD_CYC_CAP_DEFAULT 88 /* 1 micro Ah */
+
+#define DGRD_TEMP_H_DEFAULT 450 /* 0.1 degrees C */
+#define DGRD_TEMP_M_DEFAULT 250 /* 0.1 degrees C */
+#define DGRD_TEMP_L_DEFAULT 50 /* 0.1 degrees C */
+#define DGRD_TEMP_VL_DEFAULT 0 /* 0.1 degrees C */
+
+#define SOC_EST_MAX_NUM_DEFAULT 5
+#define PWRCTRL_NORMAL 0x22
+#define PWRCTRL_RESET 0x23
+
+/*
+ * Originally we relied upon a fixed size table of OCV and VDR params.
+ * However the exising linux power-supply batinfo interface for getting the OCV
+ * values from DT does not have fixed amount of OCV values. Thus we use fixed
+ * parameter amount only for values provided as module params - and use this
+ * only as maximum number of parameters when values come from DT.
+ */
+#define NUM_BAT_PARAMS 23
+#define MAX_NUM_VDR_VALUES NUM_BAT_PARAMS
+
+struct pwr_regs {
+ int used_init_regs;
+ u8 vbat_init;
+ u8 vbat_init2;
+ u8 vbat_init3;
+ u8 vbat_avg;
+ u8 ibat;
+ u8 ibat_avg;
+ u8 meas_clear;
+ u8 vsys_min_avg;
+ u8 btemp_vth;
+ u8 chg_state;
+ u8 coulomb3;
+ u8 coulomb2;
+ u8 coulomb1;
+ u8 coulomb0;
+ u8 coulomb_ctrl;
+ u8 vbat_rex_avg;
+ u8 coulomb_full3;
+ u8 cc_full_clr;
+ u8 coulomb_chg3;
+ u8 bat_temp;
+ u8 dcin_stat;
+ u8 dcin_collapse_limit;
+ u8 chg_set1;
+ u8 chg_en;
+ u8 vbat_alm_limit_u;
+ u8 batcap_mon_limit_u;
+ u8 conf;
+ u8 vdcin;
+#ifdef PWRCTRL_HACK
+ u8 pwrctrl;
+#endif
+};
+
+/* Regions for High, Medium, Low, Very Low temperature */
+enum {
+ VDR_TEMP_HIGH,
+ VDR_TEMP_NORMAL,
+ VDR_TEMP_LOW,
+ VDR_TEMP_VERY_LOW,
+ NUM_VDR_TEMPS
+};
+
+/*
+ * This works as long as we have only one instance of this driver (which is
+ * likely to be the case even with DT originated battery info). Anyways,
+ * consider moving these in allocated data just to pretend to know what I am
+ * doing XD
+ */
+static int vdr_temps[NUM_VDR_TEMPS] = { -EINVAL, -EINVAL, -EINVAL, -EINVAL};
+static int g_num_vdr_params;
+
+static struct pwr_regs pwr_regs_bd71827 = {
+ .vbat_init = BD71827_REG_VM_OCV_PRE_U,
+ .vbat_init2 = BD71827_REG_VM_OCV_PST_U,
+ .vbat_init3 = BD71827_REG_VM_OCV_PWRON_U,
+ .used_init_regs = 3,
+ .vbat_avg = BD71827_REG_VM_SA_VBAT_U,
+ .ibat = BD71827_REG_CC_CURCD_U,
+ .ibat_avg = BD71827_REG_CC_SA_CURCD_U,
+ .meas_clear = BD71827_REG_VM_SA_MINMAX_CLR,
+ .vsys_min_avg = BD71827_REG_VM_SA_VSYS_MIN_U,
+ .btemp_vth = BD71827_REG_VM_BTMP,
+ .chg_state = BD71827_REG_CHG_STATE,
+ .coulomb3 = BD71827_REG_CC_CCNTD_3,
+ .coulomb2 = BD71827_REG_CC_CCNTD_2,
+ .coulomb1 = BD71827_REG_CC_CCNTD_1,
+ .coulomb0 = BD71827_REG_CC_CCNTD_0,
+ .coulomb_ctrl = BD71827_REG_CC_CTRL,
+ .coulomb_full3 = BD71827_REG_FULL_CCNTD_3,
+ .cc_full_clr = BD71827_REG_FULL_CTRL,
+ .coulomb_chg3 = BD71827_REG_CCNTD_CHG_3,
+ .bat_temp = BD71827_REG_BAT_TEMP,
+ .dcin_stat = BD71827_REG_DCIN_STAT,
+ .dcin_collapse_limit = BD71827_REG_DCIN_CLPS,
+ .chg_set1 = BD71827_REG_CHG_SET1,
+ .chg_en = BD71827_REG_CHG_SET1,
+ .vbat_alm_limit_u = BD71827_REG_ALM_VBAT_TH_U,
+ .batcap_mon_limit_u = BD71827_REG_CC_BATCAP1_TH_U,
+ .conf = BD71827_REG_CONF,
+ .vdcin = BD71827_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+ .pwrctrl = BD71827_REG_PWRCTRL,
+ .hibernate_mask = 0x1,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71828 = {
+ .vbat_init = BD71828_REG_VBAT_INITIAL1_U,
+ .vbat_init2 = BD71828_REG_VBAT_INITIAL2_U,
+ .vbat_init3 = BD71828_REG_OCV_PWRON_U,
+ .used_init_regs = 3,
+ .vbat_avg = BD71828_REG_VBAT_U,
+ .ibat = BD71828_REG_IBAT_U,
+ .ibat_avg = BD71828_REG_IBAT_AVG_U,
+ .meas_clear = BD71828_REG_MEAS_CLEAR,
+ .vsys_min_avg = BD71828_REG_VSYS_MIN_AVG_U,
+ .btemp_vth = BD71828_REG_VM_BTMP_U,
+ .chg_state = BD71828_REG_CHG_STATE,
+ .coulomb3 = BD71828_REG_CC_CNT3,
+ .coulomb2 = BD71828_REG_CC_CNT2,
+ .coulomb1 = BD71828_REG_CC_CNT1,
+ .coulomb0 = BD71828_REG_CC_CNT0,
+ .coulomb_ctrl = BD71828_REG_COULOMB_CTRL,
+ .vbat_rex_avg = BD71828_REG_VBAT_REX_AVG_U,
+ .coulomb_full3 = BD71828_REG_CC_CNT_FULL3,
+ .cc_full_clr = BD71828_REG_COULOMB_CTRL2,
+ .coulomb_chg3 = BD71828_REG_CC_CNT_CHG3,
+ .bat_temp = BD71828_REG_BAT_TEMP,
+ .dcin_stat = BD71828_REG_DCIN_STAT,
+ .dcin_collapse_limit = BD71828_REG_DCIN_CLPS,
+ .chg_set1 = BD71828_REG_CHG_SET1,
+ .chg_en = BD71828_REG_CHG_EN,
+ .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U,
+ .batcap_mon_limit_u = BD71828_REG_BATCAP_MON_LIMIT_U,
+ .conf = BD71828_REG_CONF,
+ .vdcin = BD71828_REG_VDCIN_U,
+#ifdef PWRCTRL_HACK
+ .pwrctrl = BD71828_REG_PS_CTRL_1,
+ .hibernate_mask = 0x2,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71815 = {
+ .vbat_init = BD71815_REG_VM_OCV_PRE_U,
+ .vbat_init2 = BD71815_REG_VM_OCV_PST_U,
+ .used_init_regs = 2,
+ .vbat_avg = BD71815_REG_VM_SA_VBAT_U,
+ /* BD71815 does not have separate current and current avg */
+ .ibat = BD71815_REG_CC_CURCD_U,
+ .ibat_avg = BD71815_REG_CC_CURCD_U,
+
+ .meas_clear = BD71815_REG_VM_SA_MINMAX_CLR,
+ .vsys_min_avg = BD71815_REG_VM_SA_VSYS_MIN_U,
+ .btemp_vth = BD71815_REG_VM_BTMP,
+ .chg_state = BD71815_REG_CHG_STATE,
+ .coulomb3 = BD71815_REG_CC_CCNTD_3,
+ .coulomb2 = BD71815_REG_CC_CCNTD_2,
+ .coulomb1 = BD71815_REG_CC_CCNTD_1,
+ .coulomb0 = BD71815_REG_CC_CCNTD_0,
+ .coulomb_ctrl = BD71815_REG_CC_CTRL,
+ .vbat_rex_avg = BD71815_REG_REX_SA_VBAT_U,
+ .coulomb_full3 = BD71815_REG_FULL_CCNTD_3,
+ .cc_full_clr = BD71815_REG_FULL_CTRL,
+ .coulomb_chg3 = BD71815_REG_CCNTD_CHG_3,
+ .bat_temp = BD71815_REG_BAT_TEMP,
+ .dcin_stat = BD71815_REG_DCIN_STAT,
+ .dcin_collapse_limit = BD71815_REG_DCIN_CLPS,
+ .chg_set1 = BD71815_REG_CHG_SET1,
+ .chg_en = BD71815_REG_CHG_SET1,
+ .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U,
+ .batcap_mon_limit_u = BD71815_REG_CC_BATCAP1_TH_U,
+ .conf = BD71815_REG_CONF,
+
+ .vdcin = BD71815_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+ #error "Not implemented for BD71815"
+#endif
+};
+
+/*
+ * unit 0.1%
+ *
+ * These are the SOCs we use at zero-correction. If OCV is given via DT
+ * we interpolate the OCV tables to get the OCV corresponding these SOCs.
+ *
+ * If VDR tables are given we will ovrride these SOCs by SOCs corresponding
+ * the VDR values.
+ */
+static int soc_table_default[NUM_BAT_PARAMS] = {
+ 1000,
+ 1000,
+ 950,
+ 900,
+ 850,
+ 800,
+ 750,
+ 700,
+ 650,
+ 600,
+ 550,
+ 500,
+ 450,
+ 400,
+ 350,
+ 300,
+ 250,
+ 200,
+ 150,
+ 100,
+ 50,
+ 0,
+ -50
+};
+
+/* Module parameters */
+static int use_load_bat_params;
+static int param_thr_voltage;
+static int param_max_voltage;
+static int param_min_voltage;
+
+static int battery_cap_mah;
+
+static int dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT;
+
+static int soc_est_max_num;
+static int ocv_table[NUM_BAT_PARAMS];
+static int soc_table[NUM_BAT_PARAMS];
+static int vdr_table_h[NUM_BAT_PARAMS];
+static int vdr_table_m[NUM_BAT_PARAMS];
+static int vdr_table_l[NUM_BAT_PARAMS];
+static int vdr_table_vl[NUM_BAT_PARAMS];
+
+struct bd71827_power {
+ struct simple_gauge *sw;
+ struct simple_gauge_desc gdesc;
+ struct simple_gauge_ops ops;
+ struct regmap *regmap;
+ enum rohm_chip_type chip_type;
+ struct device *dev;
+ struct power_supply *ac; /**< alternating current power */
+ int gauge_delay; /**< Schedule to call gauge algorithm */
+ int relax_time; /**< Relax Time */
+
+ struct pwr_regs *regs;
+ /* Reg val to uA */
+ int curr_factor;
+ int rsens;
+ int min_voltage;
+ int max_voltage;
+ int low_thr_voltage;
+ int (*get_temp)(struct bd71827_power *pwr, int *temp);
+ int (*bat_inserted)(struct bd71827_power *pwr);
+ int battery_cap;
+ struct power_supply_battery_info batinfo;
+};
+
+#define __CC_to_UAH(pwr, cc) \
+({ \
+ u64 __tmp = ((u64)(cc)) * 1000000000LLU; \
+ \
+ do_div(__tmp, (pwr)->rsens * 36 / 1000); \
+ __tmp; \
+})
+
+#define CC16_to_UAH(pwe, cc) ((int)__CC_to_UAH((pwr), (cc)))
+#define CC32_to_UAH(pwe, cc) ((int)(__CC_to_UAH((pwr), (cc)) >> 16))
+
+/*
+ * rsens is typically tens of Mohms so dividing by 1000 should be ok. (usual
+ * values are 10 and 30 Mohms so division is likely to go even). We do this
+ * to avoid doing two do_divs which would be unnecessary performance hit
+ * even if this should not be time critical.
+ */
+#define UAH_to_CC(pwr, uah) ({ \
+ u64 __tmp = (uah); \
+ u32 __rs = (pwr)->rsens / 1000; \
+ __tmp *= ((u64)__rs) * 36LLU; \
+ \
+ do_div(__tmp, 1000000000); \
+ (int)__tmp; \
+})
+
+#define CALIB_NORM 0
+#define CALIB_START 1
+#define CALIB_GO 2
+
+enum {
+ STAT_POWER_ON,
+ STAT_INITIALIZED,
+};
+
+static int bd7182x_write16(struct bd71827_power *pwr, int reg, uint16_t val)
+{
+ __be16 tmp;
+
+ tmp = cpu_to_be16(val);
+
+ return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+}
+
+static int bd7182x_read16_himask(struct bd71827_power *pwr, int reg, int himask,
+ uint16_t *val)
+{
+ struct regmap *regmap = pwr->regmap;
+ int ret;
+ __be16 rvals;
+ u8 *tmp = (u8 *) &rvals;
+
+ ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val));
+ if (!ret) {
+ *tmp &= himask;
+ *val = be16_to_cpu(rvals);
+ }
+ return ret;
+}
+
+#define MAX_INITIAL_OCV_REGS 3
+/* get initial battery voltage and current */
+static int bd71827_get_init_voltage(struct bd71827_power *pwr,
+ int *ocv)
+{
+ int ret;
+ int i;
+ u8 regs[MAX_INITIAL_OCV_REGS] = {
+ pwr->regs->vbat_init,
+ pwr->regs->vbat_init2,
+ pwr->regs->vbat_init3
+ };
+ uint16_t vals[MAX_INITIAL_OCV_REGS];
+
+ *ocv = 0;
+
+ for (i = 0; i < pwr->regs->used_init_regs; i++) {
+
+ ret = bd7182x_read16_himask(pwr, regs[i], BD7182x_MASK_VBAT_U,
+ &vals[i]);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read initial battery voltage\n");
+ return ret;
+ }
+ *ocv = MAX(vals[i], *ocv);
+
+ dev_dbg(pwr->dev, "VM_OCV_%d = %d\n", i, ((int)vals[i]) * 1000);
+ }
+
+ *ocv *= 1000;
+
+ return ret;
+}
+
+static int bd71827_get_vbat(struct bd71827_power *pwr, int *vcell)
+{
+ uint16_t tmp_vcell;
+ int ret;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg,
+ BD7182x_MASK_VBAT_U, &tmp_vcell);
+ if (ret)
+ dev_err(pwr->dev, "Failed to read battery average voltage\n");
+ else
+ *vcell = ((int)tmp_vcell) * 1000;
+
+ return ret;
+}
+
+static int bd71827_get_current_ds_adc(struct bd71827_power *pwr, int *curr, int *curr_avg)
+{
+ __be16 tmp_curr;
+ char *tmp = (char *)&tmp_curr;
+ int dir = 1;
+ int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg };
+ int *vals[] = { curr, curr_avg };
+ int ret, i;
+
+ for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr,
+ sizeof(tmp_curr));
+ if (ret)
+ break;
+
+ if (*tmp & BD7182x_MASK_CURDIR_DISCHG)
+ dir = -1;
+
+ *tmp &= BD7182x_MASK_IBAT_U;
+ tmp_curr = be16_to_cpu(tmp_curr);
+
+ *vals[i] = dir * ((int)tmp_curr) * pwr->curr_factor;
+ }
+
+ return ret;
+}
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv,
+ int temp __always_unused,
+ int *dsoc);
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv, int temp,
+ int *dsoc)
+{
+ int i = 0;
+ struct bd71827_power *pwr;
+
+ /* If ocv_table is not given try luck with batinfo */
+ if (!use_load_bat_params || !ocv_table[0]) {
+ if (!sw)
+ return -EINVAL;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ *dsoc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+ if (*dsoc < 0)
+ return *dsoc;
+
+ return 0;
+ }
+
+ /* Default or module param OCV table. We have NUM_BAT_PARAMS */
+ if (ocv > ocv_table[0]) {
+ *dsoc = soc_table[0];
+ } else {
+ for (i = 0; i < NUM_BAT_PARAMS; i++) {
+ if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) {
+ *dsoc = (soc_table[i] - soc_table[i+1]) *
+ (ocv - ocv_table[i+1]) /
+ (ocv_table[i] - ocv_table[i+1]);
+ *dsoc += soc_table[i+1];
+ break;
+ }
+ }
+ if (i == NUM_BAT_PARAMS)
+ *dsoc = soc_table[i - 1];
+ }
+
+ return 0;
+}
+
+/* Unit is tenths of degree C */
+static int bd71827_get_temp(struct simple_gauge *sw, int *temp)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ struct regmap *regmap = pwr->regmap;
+ int ret;
+ int t;
+
+ ret = regmap_read(regmap, pwr->regs->btemp_vth, &t);
+ t = 200 - t;
+
+ if (ret || t > 200) {
+ dev_err(pwr->dev, "Failed to read battery temperature\n");
+ *temp = 2000;
+ } else {
+ *temp = t * 10;
+ }
+
+ return ret;
+}
+
+/* Unit is tenths of degree C */
+static int bd71828_get_temp(struct simple_gauge *sw, int *temp)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ uint16_t t;
+ int ret;
+ int tmp = 200 * 10000;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth,
+ BD71828_MASK_VM_BTMP_U, &t);
+ if (ret || t > 3200)
+ dev_err(pwr->dev,
+ "Failed to read system min average voltage\n");
+
+ tmp -= 625ULL * (unsigned int)t;
+ *temp = tmp / 1000;
+
+ return ret;
+}
+
+static int bd71827_charge_status(struct bd71827_power *pwr,
+ int *s, int *h)
+{
+ unsigned int state;
+ int status, health;
+ int ret = 1;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state);
+ if (ret)
+ dev_err(pwr->dev, "charger status reading failed (%d)\n", ret);
+
+ state &= BD7182x_MASK_CHG_STATE;
+
+ dev_dbg(pwr->dev, "CHG_STATE %d\n", state);
+
+ switch (state) {
+ case 0x00:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x0E:
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x0F:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_FULL;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x40:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x7f:
+ default:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ }
+
+ if (s)
+ *s = status;
+ if (h)
+ *h = health;
+
+ return ret;
+}
+
+static int __write_cc(struct bd71827_power *pwr, uint16_t bcap,
+ unsigned int reg, uint32_t *new)
+{
+ int ret;
+ __be32 tmp;
+ __be16 *swap_hi = (__be16 *)&tmp;
+ uint16_t *swap_lo = swap_hi + 1;
+
+ *swap_hi = cpu_to_be16(bcap & BD7182x_MASK_CC_CCNTD_HI);
+ *swap_lo = 0;
+
+ ret = regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+ if (ret) {
+ dev_err(pwr->dev, "Failed to write coulomb counter\n");
+ return ret;
+ }
+ if (new)
+ *new = be32_to_cpu(tmp);
+
+ return ret;
+}
+
+static int write_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+ int ret;
+ uint32_t new;
+
+ ret = __write_cc(pwr, bcap, pwr->regs->coulomb3, &new);
+ if (!ret)
+ dev_dbg(pwr->dev, "CC set to 0x%x\n", (int)new);
+
+ return ret;
+}
+
+static int stop_cc(struct bd71827_power *pwr)
+{
+ struct regmap *r = pwr->regmap;
+
+ return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTENB, 0);
+}
+
+static int start_cc(struct bd71827_power *pwr)
+{
+ struct regmap *r = pwr->regmap;
+
+ return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTENB, BD7182x_MASK_CCNTENB);
+}
+
+static int update_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+ int ret;
+
+ ret = stop_cc(pwr);
+ if (ret)
+ goto err_out;
+
+ ret = write_cc(pwr, bcap);
+ if (ret)
+ goto enable_out;
+
+ ret = start_cc(pwr);
+ if (ret)
+ goto enable_out;
+
+ return 0;
+
+enable_out:
+ start_cc(pwr);
+err_out:
+ dev_err(pwr->dev, "Coulomb counter write failed (%d)\n", ret);
+ return ret;
+}
+
+static int bd71828_set_uah(struct simple_gauge *sw, int bcap)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ u16 cc_val = UAH_to_CC(pwr, bcap);
+
+ return update_cc(pwr, cc_val);
+}
+
+static int __read_cc(struct bd71827_power *pwr, u32 *cc, unsigned int reg)
+{
+ int ret;
+ __be32 tmp_cc;
+
+ ret = regmap_bulk_read(pwr->regmap, reg, &tmp_cc, sizeof(tmp_cc));
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read coulomb counter\n");
+ return ret;
+ }
+ *cc = be32_to_cpu(tmp_cc) & BD7182x_MASK_CC_CCNTD;
+
+ return 0;
+}
+
+static int read_cc_full(struct bd71827_power *pwr, u32 *cc)
+{
+ return __read_cc(pwr, cc, pwr->regs->coulomb_full3);
+}
+
+static int read_cc(struct bd71827_power *pwr, u32 *cc)
+{
+ return __read_cc(pwr, cc, pwr->regs->coulomb3);
+}
+
+/** @brief set initial coulomb counter value from battery voltage
+ * @param pwr power device
+ * @return 0
+ */
+static int calibration_coulomb_counter(struct bd71827_power *pwr)
+{
+ struct regmap *regmap = pwr->regmap;
+ u32 bcap;
+ int soc, ocv, ret = 0, tmpret = 0;
+
+ /* Get initial OCV */
+ bd71827_get_init_voltage(pwr, &ocv);
+ dev_dbg(pwr->dev, "ocv %d\n", ocv);
+
+ if (pwr->ops.get_soc_by_ocv) {
+ ret = pwr->ops.get_soc_by_ocv(NULL, ocv, 0, &soc);
+ } else {
+ soc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+ if (soc < 0)
+ return soc;
+ }
+ /* Get init soc from ocv/soc table */
+
+ dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
+ if (soc < 0)
+ soc = 0;
+
+ bcap = UAH_to_CC(pwr, pwr->battery_cap) * soc / 1000;
+ write_cc(pwr, bcap + UAH_to_CC(pwr, pwr->battery_cap) / 200);
+
+ msleep(5000);
+
+ tmpret = write_cc(pwr, bcap);
+ if (tmpret)
+ goto enable_cc_out;
+
+enable_cc_out:
+ /* Start canceling offset of the DS ADC. This needs 1 second at least */
+ ret = regmap_update_bits(regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCCALIB, BD7182x_MASK_CCCALIB);
+
+ return (tmpret) ? tmpret : ret;
+}
+
+/* This should be used to get VSYS for low limit calculations */
+static int bd71827_get_vsys_min(struct simple_gauge *sw, int *uv)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ uint16_t tmp_vcell;
+ int ret;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vsys_min_avg,
+ BD7182x_MASK_VBAT_U, &tmp_vcell);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read system min average voltage\n");
+ return ret;
+ }
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->meas_clear,
+ BD718XX_MASK_VSYS_MIN_AVG_CLR,
+ BD718XX_MASK_VSYS_MIN_AVG_CLR);
+ if (ret)
+ dev_warn(pwr->dev, "failed to clear cached Vsys\n");
+
+ *uv = ((int)tmp_vcell) * 1000;
+
+ return 0;
+}
+
+/* This should be used for relax Vbat with BD71827 */
+static int bd71827_get_voltage(struct simple_gauge *sg, int *vbat)
+{
+ int voltage, ret;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sg);
+
+ ret = bd71827_get_vbat(pwr, &voltage);
+ if (ret)
+ return ret;
+
+ *vbat = voltage;
+
+ return 0;
+}
+
+static int bd71828_get_uah_from_full(struct simple_gauge *sw, int *from_full_uah)
+{
+ int ret;
+ struct bd71827_power *pwr;
+ struct regmap *regmap;
+ u32 full_charged_coulomb_cnt;
+ u32 cc;
+ int diff_coulomb_cnt;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ regmap = pwr->regmap;
+
+ /*
+ * Read and clear the stored CC value from moment when battery was
+ * last charged to full.
+ */
+ ret = read_cc_full(pwr, &full_charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "failed to read full coulomb counter\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, pwr->regs->cc_full_clr,
+ BD7182x_MASK_CC_FULL_CLR,
+ BD7182x_MASK_CC_FULL_CLR);
+ /* Get current CC value to estimate change of charge since full */
+ ret = read_cc(pwr, &cc);
+ if (ret)
+ return ret;
+
+ diff_coulomb_cnt = full_charged_coulomb_cnt - cc;
+
+ diff_coulomb_cnt >>= 16;
+
+ /*
+ * Ignore possible increase in CC which can be caused by ADC offset or
+ * temperature change
+ */
+ if (diff_coulomb_cnt > 0)
+ diff_coulomb_cnt = 0;
+
+ *from_full_uah = CC16_to_UAH(pwr, diff_coulomb_cnt);
+
+ return 0;
+}
+
+static int bd71828_get_uah(struct simple_gauge *sw, int *uah)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ u32 cc;
+ int ret;
+
+ ret = read_cc(pwr, &cc);
+ if (!ret)
+ *uah = CC32_to_UAH(pwr, cc);
+
+ return ret;
+}
+
+/*
+ * Standard batinfo supports only accuracy of 1% for SOC - which
+ * may not be sufficient for us. SWGAUGE provides soc in unts of 0.1% here
+ * to allow more accurate computation.
+ */
+static int bd71827_get_ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv)
+{
+ int i = 0;
+ struct bd71827_power *pwr;
+
+ /* If soc_table is not given try luck with batinfo */
+ if (!use_load_bat_params || !ocv_table[0]) {
+ if (!sw)
+ return -EINVAL;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ *ocv = power_supply_batinfo_dcap2ocv(&pwr->batinfo, dsoc, temp);
+ if (*ocv < 0)
+ return *ocv;
+
+ return 0;
+ }
+
+ /* Default or module param OCV table. We have NUM_BAT_PARAMS */
+
+ if (dsoc > soc_table[0]) {
+ *ocv = pwr->max_voltage;
+ return 0;
+ }
+ if (dsoc == 0) {
+ *ocv = ocv_table[NUM_BAT_PARAMS - 2];
+ return 0;
+ }
+
+ i = 0;
+ while (i < NUM_BAT_PARAMS - 1) {
+ if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+ *ocv = (ocv_table[i] - ocv_table[i+1]) *
+ (dsoc - soc_table[i+1]) / (soc_table[i] -
+ soc_table[i+1]) + ocv_table[i+1];
+ return 0;
+ }
+ i++;
+ }
+
+ *ocv = ocv_table[NUM_BAT_PARAMS - 1];
+
+ return 0;
+}
+
+static void calc_vdr(int *res, int *vdr, int temp, int dgrd_temp,
+ int *vdr_hi, int dgrd_temp_hi, int items)
+{
+ int i;
+
+ /* Get VDR weighed by temperature */
+ for (i = 0; i < items; i++)
+ res[i] = LINEAR_INTERPOLATE(vdr_hi[i], vdr[i], dgrd_temp_hi,
+ dgrd_temp, temp);
+}
+
+/* get VDR(Voltage Drop Rate) value by SOC */
+static int bd71827_get_vdr(struct bd71827_power *pwr, int dsoc, int temp)
+{
+ int i = 0;
+ int vdr = 100;
+ int vdr_table[NUM_BAT_PARAMS];
+
+ /* Calculate VDR by temperature */
+ if (temp >= vdr_temps[VDR_TEMP_HIGH])
+ for (i = 0; i < g_num_vdr_params; i++)
+ vdr_table[i] = vdr_table_h[i];
+ else if (temp >= vdr_temps[VDR_TEMP_NORMAL])
+ calc_vdr(vdr_table, vdr_table_m, temp, vdr_temps[VDR_TEMP_NORMAL],
+ vdr_table_h, vdr_temps[VDR_TEMP_HIGH],
+ g_num_vdr_params);
+ else if (temp >= vdr_temps[VDR_TEMP_LOW])
+ calc_vdr(vdr_table, vdr_table_l, temp, vdr_temps[VDR_TEMP_LOW],
+ vdr_table_m, vdr_temps[VDR_TEMP_NORMAL],
+ g_num_vdr_params);
+ else if (temp >= vdr_temps[VDR_TEMP_VERY_LOW])
+ calc_vdr(vdr_table, vdr_table_vl, temp,
+ vdr_temps[VDR_TEMP_VERY_LOW], vdr_table_l,
+ vdr_temps[VDR_TEMP_LOW], g_num_vdr_params);
+ else
+ for (i = 0; i < g_num_vdr_params; i++)
+ vdr_table[i] = vdr_table_vl[i];
+
+ if (dsoc > soc_table[0]) {
+ vdr = 100;
+ } else if (dsoc == 0) {
+ vdr = vdr_table[g_num_vdr_params - 1];
+ } else {
+ for (i = 0; i < g_num_vdr_params - 1; i++)
+ if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+ vdr = LINEAR_INTERPOLATE(vdr_table[i],
+ vdr_table[i+1],
+ soc_table[i],
+ soc_table[i+1], dsoc);
+
+ break;
+ }
+ if (i == g_num_vdr_params - 1)
+ vdr = vdr_table[i];
+ }
+ dev_dbg(pwr->dev, "vdr = %d\n", vdr);
+ return vdr;
+}
+
+static int bd71828_zero_correct(struct simple_gauge *sw, int *effective_cap,
+ int cc_uah, int vbat, int temp)
+{
+ int ocv_table_load[NUM_BAT_PARAMS];
+ int i, ret;
+ /* Assume fixed-size module param table */
+ static int params = NUM_BAT_PARAMS;
+ int ocv;
+ int dsoc;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ /*
+ * Calculate SOC from CC and effective battery cap.
+ * Use unit of 0.1% for dsoc to improve accuracy
+ */
+ dsoc = CAP2DSOC(cc_uah, *effective_cap);
+ dev_dbg(pwr->dev, "dsoc = %d\n", dsoc);
+
+ ret = bd71827_get_ocv(sw, dsoc, 0, &ocv);
+ if (ret)
+ return ret;
+
+ if (!ocv_table[0]) {
+ for (i = 0; i < g_num_vdr_params; i++)
+ ocv_table[i] = power_supply_batinfo_dcap2ocv(&pwr->batinfo,
+ soc_table[i], temp);
+ /*
+ * Update amount of OCV values id we didn't have the fixed size
+ * module param table
+ */
+ params = g_num_vdr_params;
+ }
+ for (i = 1; i < params; i++) {
+ ocv_table_load[i] = ocv_table[i] - (ocv - vbat);
+ if (ocv_table_load[i] <= pwr->min_voltage) {
+ dev_dbg(pwr->dev, "ocv_table_load[%d] = %d\n", i,
+ ocv_table_load[i]);
+ break;
+ }
+ }
+
+ /*
+ * For improved accuracy ROHM helps customers to measure some
+ * battery voltage drop curves to do further SOC estimation improvement.
+ * If VDR tables are available we perform these corrections.
+ */
+ if (i < params) {
+ int zero_soc_pos;
+ int j, k, m;
+ int dv;
+ int lost_cap, new_lost_cap;
+ int dsoc0;
+ int vdr, vdr0;
+ int soc_range;
+
+ /*
+ * The original ROHM algorithm had fixed amount of OCV and VDR
+ * values. The quiet expectation of the algorithm was that the
+ * second last value in these tables correspond zero SOC. In
+ * order to relax this assumption when values come from DT we
+ * try to scan the SOC table for zero SOC.
+ */
+ for (zero_soc_pos = params - 1; zero_soc_pos >= 0;
+ zero_soc_pos--)
+ if (soc_table[zero_soc_pos] >= 0)
+ break;
+
+ if (soc_table[zero_soc_pos])
+ dev_warn_once(pwr->dev,
+ "VDR/OCV: zero SOC not found\n");
+
+ /*
+ * We want to know the zero soc position from the last entry
+ * in SOC table so that we know where the fully depleted cap
+ * is met.
+ */
+ zero_soc_pos = params - zero_soc_pos;
+
+ soc_range = (soc_table[i - 1] - soc_table[i]) / 10;
+ if (soc_range < 1) {
+ dev_err_once(pwr->dev, "Bad SOC table values %u, %u\n",
+ soc_table[i - 1], soc_table[i]);
+ return -EINVAL;
+ }
+ dv = (ocv_table_load[i - 1] - ocv_table_load[i]) / soc_range; /* was hard coded 5 */
+ for (j = 1; j < soc_range/* was 5 */; j++) {
+ if ((ocv_table_load[i] + dv * j) >
+ pwr->min_voltage) {
+ break;
+ }
+ }
+
+ lost_cap = ((params - zero_soc_pos - i) * soc_range /* was 5 */ +
+ (j - 1)) * *effective_cap / 100;
+ dev_dbg(pwr->dev, "lost_cap-1 = %d\n", lost_cap);
+
+ for (m = 0; m < soc_est_max_num; m++) {
+ new_lost_cap = lost_cap;
+ dsoc0 = CAP2DSOC(lost_cap, *effective_cap);
+ if ((dsoc >= 0 && dsoc0 > dsoc) ||
+ (dsoc < 0 && dsoc0 < dsoc))
+ dsoc0 = dsoc;
+
+ dev_dbg(pwr->dev, "dsoc0(%d) = %d\n", m, dsoc0);
+
+ vdr = bd71827_get_vdr(pwr, dsoc, temp);
+ vdr0 = bd71827_get_vdr(pwr, dsoc0, temp);
+
+ for (k = 1; k < params; k++) {
+ if (!vdr) {
+ dev_err(pwr->dev,
+ "VDR zero-correction failed\n");
+ break;
+ }
+ ocv_table_load[k] = ocv_table[k] -
+ (ocv - vbat) * vdr0 / vdr;
+ if (ocv_table_load[k] <= pwr->min_voltage) {
+ dev_dbg(pwr->dev,
+ "ocv_table_load[%d] = %d\n", k,
+ ocv_table_load[k]);
+ break;
+ }
+ }
+ if (k < params) {
+ dv = (ocv_table_load[k-1] -
+ ocv_table_load[k]) / 5;
+ for (j = 1; j < 5; j++)
+ if ((ocv_table_load[k] + dv * j) >
+ pwr->min_voltage)
+ break;
+
+ new_lost_cap = ((params - zero_soc_pos - k) *
+ 5 + (j - 1)) *
+ *effective_cap / 100;
+ if (soc_est_max_num == 1)
+ lost_cap = new_lost_cap;
+ else
+ lost_cap += (new_lost_cap - lost_cap) /
+ (2 * (soc_est_max_num - m));
+ dev_dbg(pwr->dev, "lost_cap-2(%d) = %d\n", m,
+ lost_cap);
+ }
+ if (new_lost_cap == lost_cap)
+ break;
+ }
+
+ *effective_cap -= lost_cap;
+ }
+
+ return 0;
+}
+
+static int get_chg_online(struct bd71827_power *pwr, int *chg_online)
+{
+ int r, ret;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read DCIN status\n");
+ return ret;
+ }
+ *chg_online = ((r & BD7182x_MASK_DCIN_DET) != 0);
+
+ return 0;
+}
+
+static int get_bat_online(struct bd71827_power *pwr, int *bat_online)
+{
+ int r, ret;
+
+#define BAT_OPEN 0x7
+ ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read battery temperature\n");
+ return ret;
+ }
+ *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN);
+
+ return 0;
+}
+
+static int bd71828_bat_inserted(struct bd71827_power *pwr)
+{
+ int ret, val;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read CONF register\n");
+ return 0;
+ }
+ ret = val & BD7182x_MASK_CONF_PON;
+
+ if (ret)
+ regmap_update_bits(pwr->regmap, pwr->regs->conf,
+ BD7182x_MASK_CONF_PON, 0);
+
+ return ret;
+}
+
+static int bd71815_bat_inserted(struct bd71827_power *pwr)
+{
+ int ret, val;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read CONF register\n");
+ return ret;
+ }
+
+ ret = !(val & BD71815_MASK_CONF_XSTB);
+ if (ret)
+ regmap_write(pwr->regmap, pwr->regs->conf, val |
+ BD71815_MASK_CONF_XSTB);
+
+ return ret;
+}
+
+static int bd71827_init_hardware(struct bd71827_power *pwr)
+{
+ int ret;
+
+ /* TODO: Collapse limit should come from device-tree ? */
+ ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit,
+ BD7182x_DCIN_COLLAPSE_DEFAULT);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to write DCIN collapse limit\n");
+ return ret;
+ }
+
+ ret = pwr->bat_inserted(pwr);
+ if (ret < 0)
+ return ret;
+
+ if (ret) {
+ int cc_val;
+
+ /* Ensure Coulomb Counter is stopped */
+ ret = stop_cc(pwr);
+ if (ret)
+ return ret;
+
+ /* Set Coulomb Counter Reset bit*/
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTRST,
+ BD7182x_MASK_CCNTRST);
+ if (ret)
+ return ret;
+
+ /* Clear Coulomb Counter Reset bit*/
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTRST, 0);
+ if (ret)
+ return ret;
+
+ /* Set initial Coulomb Counter by HW OCV */
+ calibration_coulomb_counter(pwr);
+
+ /* WDT_FST auto set */
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1,
+ BD7182x_MASK_WDT_AUTO,
+ BD7182x_MASK_WDT_AUTO);
+ if (ret)
+ return ret;
+
+ ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u,
+ VBAT_LOW_TH);
+ if (ret)
+ return ret;
+
+ /* Set monitor threshold to 9/10 of battery uAh capacity */
+ cc_val = UAH_to_CC(pwr, pwr->battery_cap);
+
+ /*
+ * On BD71815 "we mask the power-state" from relax detection.
+ * I am unsure what the impact of the power-state would be if
+ * we didn't - but this is what the vendor driver did - and
+ * that driver has been used in few projects so I just assume
+ * this is needed.
+ */
+ if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) {
+ ret = regmap_set_bits(pwr->regmap,
+ BD71815_REG_REX_CTRL_1,
+ REX_PMU_STATE_MASK);
+ if (ret)
+ return ret;
+ }
+
+ ret = bd7182x_write16(pwr, pwr->regs->batcap_mon_limit_u,
+ cc_val * 9 / 10);
+ if (ret)
+ return ret;
+
+ dev_dbg(pwr->dev, "REG_CC_BATCAP1_TH = %d\n",
+ (cc_val * 9 / 10));
+ }
+
+ return start_cc(pwr);
+}
+
+#define MK_2_100MCELSIUS(m_kevl_in) (((int)(m_kevl_in) - 273150) / 100)
+static int get_vdr_from_dt(struct bd71827_power *pwr,
+ struct fwnode_handle *node, int temp_values)
+{
+ int i, ret, num_values, *tmp_table;
+ u32 vdr_kelvin[NUM_VDR_TEMPS];
+
+ if (temp_values != NUM_VDR_TEMPS) {
+ dev_err(pwr->dev, "Bad VDR temperature table size (%d). Expected %d",
+ temp_values, NUM_VDR_TEMPS);
+ return -EINVAL;
+ }
+ ret = fwnode_property_read_u32_array(node,
+ "rohm,volt-drop-temp-millikelvin",
+ &vdr_kelvin[0], NUM_VDR_TEMPS);
+ if (ret) {
+ dev_err(pwr->dev, "Invalid VDR temperatures in device-tree");
+ return ret;
+ }
+ /* Convert millikelvin to .1 celsius as is expected by VDR algo */
+ for (i = 0; i < NUM_VDR_TEMPS; i++)
+ vdr_temps[i] = MK_2_100MCELSIUS(vdr_kelvin[i]);
+
+ num_values = fwnode_property_count_u32(node, "rohm,volt-drop-soc");
+ if (num_values <= 0 || num_values > MAX_NUM_VDR_VALUES) {
+ dev_err(pwr->dev, "malformed voltage drop parameters\n");
+ return -EINVAL;
+ }
+ g_num_vdr_params = num_values;
+
+ tmp_table = kcalloc(num_values, sizeof(int), GFP_KERNEL);
+ if (!tmp_table)
+ return -ENOMEM;
+ /*
+ * We collect NUM_VDR_TEMPS + 1 tables, all temperature tables +
+ * the SOC table
+ */
+ for (i = 0; i < NUM_VDR_TEMPS + 1; i++) {
+ int index;
+ static const char * const prop[] = {
+ /*
+ * SOC in units of 0.1 percent. TODO: Check if we have
+ * standard DT unit for percentage with higher accuracy
+ */
+ "rohm,volt-drop-soc",
+ "rohm,volt-drop-high-temp-microvolt",
+ "rohm,volt-drop-normal-temp-microvolt",
+ "rohm,volt-drop-low-temp-microvolt",
+ "rohm,volt-drop-very-low-temp-microvolt",
+ };
+ int *tables[5] = {
+ &soc_table[0], &vdr_table_h[0], &vdr_table_m[0],
+ &vdr_table_l[0], &vdr_table_vl[0]
+ };
+
+ if (num_values != fwnode_property_count_u32(node, prop[i])) {
+ dev_err(pwr->dev,
+ "%s: Bad size. Expected %d parameters",
+ prop[i], num_values);
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = fwnode_property_read_u32_array(node, prop[i], tmp_table,
+ num_values);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Invalid VDR temperatures in device-tree");
+ goto out;
+ }
+ for (index = 0; index < num_values; index++)
+ tables[i][index] = tmp_table[index];
+ }
+out:
+ kfree(tmp_table);
+
+ return ret;
+}
+
+ /* Set default parameters if no module parameters were given */
+static int bd71827_set_battery_parameters(struct bd71827_power *pwr)
+{
+ int i;
+
+ /*
+ * We support getting battery parameters from static battery node
+ * or as module parameters.
+ */
+ if (!use_load_bat_params) {
+ int ret, num_vdr;
+ struct fwnode_handle *node;
+
+ /*
+ * power_supply_dev_get_battery_info uses devm internally
+ * so we do not need explicit remove()
+ */
+ ret = power_supply_dev_get_battery_info(pwr->dev->parent, NULL,
+ &pwr->batinfo);
+ if (ret) {
+ dev_err(pwr->dev, "No battery information (%d)\n", ret);
+ return ret;
+ }
+
+ if (!pwr->batinfo.ocv_table[0]) {
+ dev_err(pwr->dev, "No Open Circuit Voltages for battery\n");
+ return -EINVAL;
+ }
+
+ if (pwr->batinfo.charge_full_design_uah == -EINVAL) {
+ dev_err(pwr->dev, "Unknown battery capacity\n");
+ return -EINVAL;
+ }
+
+ if (pwr->batinfo.voltage_max_design_uv == -EINVAL) {
+ /*
+ * We could try digging this from OCV table but
+ * lets just bail-out for now
+ */
+ dev_err(pwr->dev, "Unknown max voltage\n");
+ return -EINVAL;
+ }
+ pwr->max_voltage = pwr->batinfo.voltage_max_design_uv;
+
+ if (pwr->batinfo.voltage_min_design_uv == -EINVAL) {
+ /* We could try digging this from OCV table....? */
+ dev_err(pwr->dev, "Unknown max voltage\n");
+ return -EINVAL;
+ }
+ pwr->min_voltage = pwr->batinfo.voltage_min_design_uv;
+ /*
+ * Let's default the zero-correction limit to 10%
+ * voltage limit. TODO: See what would be the correct value
+ */
+ pwr->battery_cap = pwr->batinfo.charge_full_design_uah;
+ pwr->gdesc.degrade_cycle_uah = pwr->batinfo.degrade_cycle_uah;
+
+ soc_est_max_num = SOC_EST_MAX_NUM_DEFAULT;
+
+ node = dev_fwnode(pwr->dev->parent);
+ if (!node) {
+ dev_err(pwr->dev, "no charger node\n");
+ return -ENODEV;
+ }
+ node = fwnode_find_reference(node, "monitored-battery", 0);
+ if (IS_ERR(node)) {
+ dev_err(pwr->dev, "No battery node found\n");
+ return PTR_ERR(node);
+ }
+
+ num_vdr = fwnode_property_count_u32(node, "rohm,volt-drop-temp-millikelvin");
+ if (num_vdr > 0) {
+ ret = get_vdr_from_dt(pwr, node, num_vdr);
+ if (ret)
+ return ret;
+ } else {
+ vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+ vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+ vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+ vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+ }
+ } else {
+ if (vdr_temps[VDR_TEMP_HIGH] == -EINVAL ||
+ vdr_temps[VDR_TEMP_NORMAL] == -EINVAL ||
+ vdr_temps[VDR_TEMP_LOW] == -EINVAL ||
+ vdr_temps[VDR_TEMP_VERY_LOW] == -EINVAL) {
+ vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+ vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+ vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+ vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+ }
+
+ pwr->min_voltage = param_max_voltage;
+ pwr->max_voltage = param_min_voltage;
+ pwr->low_thr_voltage = param_thr_voltage;
+ pwr->battery_cap = battery_cap_mah * 1000;
+ pwr->gdesc.degrade_cycle_uah = dgrd_cyc_cap;
+ }
+ /* SOC table is expected to be in descending order. */
+ if (!soc_table[0])
+ for (i = 0; i < NUM_BAT_PARAMS; i++)
+ soc_table[i] = soc_table_default[i];
+
+ if (!pwr->min_voltage || !pwr->max_voltage || !pwr->battery_cap) {
+ dev_err(pwr->dev, "Battery parameters missing\n");
+
+ return -EINVAL;
+ }
+ if (!pwr->low_thr_voltage)
+ pwr->low_thr_voltage = pwr->min_voltage +
+ (pwr->max_voltage - pwr->min_voltage) / 10;
+ return 0;
+}
+
+static int bd71827_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(psy->dev.parent);
+ u32 vot;
+ uint16_t tmp;
+ int online;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = get_chg_online(pwr, &online);
+ if (!ret)
+ val->intval = online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin,
+ BD7182x_MASK_VDCIN_U, &tmp);
+ if (ret)
+ return ret;
+
+ vot = tmp;
+ /* 5 milli volt steps */
+ val->intval = 5000 * vot;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bd71827_battery_get_property(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(gauge);
+ int ret = 0;
+ int status, health, tmp, curr, curr_avg;
+
+ if (psp == POWER_SUPPLY_PROP_STATUS || psp == POWER_SUPPLY_PROP_HEALTH
+ || psp == POWER_SUPPLY_PROP_CHARGE_TYPE)
+ ret = bd71827_charge_status(pwr, &status, &health);
+ else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG ||
+ psp == POWER_SUPPLY_PROP_CURRENT_NOW)
+ ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = health;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (status == POWER_SUPPLY_STATUS_CHARGING)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = get_bat_online(pwr, &tmp);
+ if (!ret)
+ val->intval = tmp;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bd71827_get_vbat(pwr, &tmp);
+ val->intval = tmp;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = curr_avg;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = curr;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = pwr->max_voltage;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = pwr->min_voltage;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = MAX_CURRENT_DEFAULT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/** @brief ac properties */
+static enum power_supply_property bd71827_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static enum power_supply_property bd71827_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static ssize_t charging_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+ ssize_t ret = 0;
+ unsigned long val;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val != 0 && val != 1) {
+ dev_warn(dev, "use 0/1 to disable/enable charging\n");
+ return -EINVAL;
+ }
+
+ if (val)
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+ BD7182x_MASK_CHG_EN,
+ BD7182x_MASK_CHG_EN);
+ else
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+ BD7182x_MASK_CHG_EN,
+ 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t charging_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+ int chg_en, chg_online, ret;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en);
+ if (ret)
+ return ret;
+
+ ret = get_chg_online(pwr, &chg_online);
+ if (ret)
+ return ret;
+
+ chg_en &= BD7182x_MASK_CHG_EN;
+ return sprintf(buf, "%x\n", chg_online && chg_en);
+}
+
+static DEVICE_ATTR_RW(charging);
+
+static struct attribute *bd71827_sysfs_attributes[] = {
+ &dev_attr_charging.attr, NULL,
+};
+
+static const struct attribute_group bd71827_sysfs_attr_group = {
+ .attrs = bd71827_sysfs_attributes,
+};
+
+static const struct attribute_group *bd71827_sysfs_attr_groups[] = {
+ &bd71827_sysfs_attr_group, NULL,
+};
+
+/** @brief powers supplied by bd71827_ac */
+static char *bd71827_ac_supplied_to[] = {
+ BAT_NAME,
+};
+
+static const struct power_supply_desc bd71827_ac_desc = {
+ .name = AC_NAME,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = bd71827_charger_props,
+ .num_properties = ARRAY_SIZE(bd71827_charger_props),
+ .get_property = bd71827_charger_get_property,
+};
+
+static const struct simple_gauge_psy gauge_psy_config = {
+ .psy_name = BAT_NAME,
+ .additional_props = bd71827_battery_props,
+ .num_additional_props = ARRAY_SIZE(bd71827_battery_props),
+ .get_custom_property = bd71827_battery_get_property,
+};
+#ifdef PWRCTRL_HACK
+/*
+ * This is not-so-pretty hack for allowing external code to call
+ * bd71827_chip_hibernate() without this power device-data
+ */
+static struct bd71827_power *hack;
+static DEFINE_SPINLOCK(pwrlock);
+
+static struct bd71827_power *get_power(void)
+{
+ mutex_lock(&pwrlock);
+ if (!hack) {
+ mutex_unlock(&pwrlock);
+ return -ENOENT;
+ }
+ return hack;
+}
+
+static void put_power(void)
+{
+ mutex_unlock(&pwrlock);
+}
+
+static int set_power(struct bd71827_power *pwr)
+{
+ mutex_lock(&pwrlock);
+ hack = pwr;
+ mutex_unlock(&pwrlock);
+}
+
+static void free_power(void)
+{
+ mutex_lock(&pwrlock);
+ hack = NULL;
+ mutex_unlock(&pwrlock);
+}
+
+/*
+ * TODO: Find the corret way to do this
+ */
+void bd71827_chip_hibernate(void)
+{
+ struct bd71827_power *pwr = get_power();
+
+ if (IS_ERR(pwr)) {
+ pr_err("%s called before probe finished\n", __func__);
+ return PTR_ERR(pwr);
+ }
+
+ /* programming sequence in EANAB-151 */
+ regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+ pwr->regs->hibernate_mask, 0);
+ regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+ pwr->regs->hibernate_mask,
+ pwr->regs->hibernate_mask);
+ put_power();
+}
+#endif
+
+#define RSENS_CURR 10000000000LLU
+
+#define BD_ISR_NAME(name) \
+bd7181x_##name##_isr
+
+#define BD_ISR_BAT(name, print, run_gauge) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71827_power *pwr = (struct bd71827_power *)data; \
+ \
+ if (run_gauge) \
+ simple_gauge_run(pwr->sw); \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->sw->psy); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_AC(name, print, run_gauge) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71827_power *pwr = (struct bd71827_power *)data; \
+ \
+ if (run_gauge) \
+ simple_gauge_run(pwr->sw); \
+ power_supply_changed(pwr->ac); \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->sw->psy); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_DUMMY(name, print) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71827_power *pwr = (struct bd71827_power *)data; \
+ \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ \
+ return IRQ_HANDLED; \
+}
+
+BD_ISR_BAT(chg_state_changed, "CHG state changed", true)
+/* DCIN voltage changes */
+BD_ISR_AC(dcin_removed, "DCIN removed", true)
+BD_ISR_AC(clps_out, "DCIN voltage back to normal", true)
+BD_ISR_AC(clps_in, "DCIN voltage collapsed", false)
+BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true)
+BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true)
+
+BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold")
+BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold")
+
+BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared")
+BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage")
+BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared")
+BD_ISR_DUMMY(vsys_low_det, "VSYS low")
+BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed")
+BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected")
+BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true)
+BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true)
+BD_ISR_BAT(bat_removed, "Battery removed", true)
+BD_ISR_BAT(bat_det, "Battery detected", true)
+/* TODO: Verify the meaning of these interrupts */
+BD_ISR_BAT(rechg_det, "Recharging", true)
+BD_ISR_BAT(rechg_res, "Recharge ending", true)
+BD_ISR_DUMMY(temp_transit, "Temperature transition")
+BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false)
+BD_ISR_BAT(therm_det, "bd71815-therm-det", true)
+BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false)
+BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true)
+BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false)
+BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true)
+BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true)
+BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true)
+/* What should we do here? */
+BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false)
+BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true)
+BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true)
+BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false)
+BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true)
+BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false)
+BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true)
+BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false)
+BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true)
+BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false)
+BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true)
+BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true)
+BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true)
+BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true)
+
+static irqreturn_t bd7182x_dcin_removed(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ simple_gauge_run(pwr->sw);
+ power_supply_changed(pwr->ac);
+ dev_dbg(pwr->dev, "DCIN removed\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd718x7_chg_done(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ /*
+ * Battery is likely to be FULL => run simple_gauge to initiate
+ * CC setting
+ */
+ simple_gauge_run(pwr->sw);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd7182x_dcin_detected(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "DCIN inserted\n");
+ power_supply_changed(pwr->ac);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Resumed\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Detected\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_warn(pwr->dev, "Overtemp Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Overtemp Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+struct bd7182x_irq_res {
+ const char *name;
+ irq_handler_t handler;
+};
+
+#define BDIRQ(na, hn) { .name = (na), .handler = (hn) }
+
+static int bd7182x_get_irqs(struct platform_device *pdev,
+ struct bd71827_power *pwr)
+{
+ int i, irq, ret;
+ static const struct bd7182x_irq_res bd71815_irqs[] = {
+ BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)),
+ BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)),
+ BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)),
+ BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)),
+ BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)),
+ BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)),
+ BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)),
+
+ BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)),
+ BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)),
+ BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)),
+ BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)),
+ BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)),
+ BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)),
+ BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)),
+ BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)),
+ BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)),
+ BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)),
+ BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)),
+ BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)),
+ BDIRQ("bd71815-bat-temp-normal", bd71827_temp_bat_hi_res),
+ BDIRQ("bd71815-bat-temp-erange", bd71827_temp_bat_hi_det),
+ BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)),
+ BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)),
+
+ /* Add ISRs for these */
+ BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)),
+ BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)),
+ BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)),
+ BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)),
+ BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)),
+ BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)),
+ BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)),
+ BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)),
+ BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)),
+ BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)),
+ BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)),
+ /* cc-mon 1 & 3 ? */
+ BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)),
+ BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)),
+ BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)),
+ BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)),
+ BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)),
+ BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)),
+ BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)),
+ BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)),
+ BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)),
+ BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)),
+ BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)),
+ /*
+ * TODO: add rest of the IRQs and re-check the handling.
+ * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3,
+ * bd71815-bat-low-res, bd71815-bat-low-det,
+ * bd71815-bat-hi-res, bd71815-bat-hi-det.
+ */
+ };
+ static const struct bd7182x_irq_res bd71828_irqs[] = {
+ BDIRQ("bd71828-chg-done", bd718x7_chg_done),
+ BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected),
+ BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed),
+ BDIRQ("bd71828-vbat-normal", bd71827_vbat_low_res),
+ BDIRQ("bd71828-vbat-low", bd71827_vbat_low_det),
+ BDIRQ("bd71828-btemp-hi", bd71827_temp_bat_hi_det),
+ BDIRQ("bd71828-btemp-cool", bd71827_temp_bat_hi_res),
+ BDIRQ("bd71828-btemp-lo", bd71827_temp_bat_low_det),
+ BDIRQ("bd71828-btemp-warm", bd71827_temp_bat_low_res),
+ BDIRQ("bd71828-temp-hi", bd71827_temp_vf_det),
+ BDIRQ("bd71828-temp-norm", bd71827_temp_vf_res),
+ BDIRQ("bd71828-temp-125-over", bd71827_temp_vf125_det),
+ BDIRQ("bd71828-temp-125-under", bd71827_temp_vf125_res),
+ };
+ int num_irqs;
+ const struct bd7182x_irq_res *irqs;
+
+
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71827:
+ case ROHM_CHIP_TYPE_BD71828:
+ irqs = &bd71828_irqs[0];
+ num_irqs = ARRAY_SIZE(bd71828_irqs);
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ irqs = &bd71815_irqs[0];
+ num_irqs = ARRAY_SIZE(bd71815_irqs);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_irqs; i++) {
+ irq = platform_get_irq_byname(pdev, irqs[i].name);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ irqs[i].handler, 0,
+ irqs[i].name, pwr);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+#define RSENS_DEFAULT_30MOHM 30000000
+
+static int bd7182x_get_rsens(struct bd71827_power *pwr)
+{
+ u64 tmp = RSENS_CURR;
+ int rsens_ohm = RSENS_DEFAULT_30MOHM;
+ struct fwnode_handle *node = NULL;
+
+ if (pwr->dev->parent)
+ node = dev_fwnode(pwr->dev->parent);
+
+ if (node) {
+ int ret;
+ uint32_t rs;
+
+ ret = fwnode_property_read_u32(node,
+ "rohm,charger-sense-resistor-ohms",
+ &rs);
+ if (ret) {
+ if (ret == -EINVAL) {
+ rs = RSENS_DEFAULT_30MOHM;
+ } else {
+ dev_err(pwr->dev, "Bad RSENS dt property\n");
+ return ret;
+ }
+ }
+ if (!rs) {
+ dev_err(pwr->dev, "Bad RSENS value\n");
+ return -EINVAL;
+ }
+
+ rsens_ohm = (int)rs;
+ }
+
+ /* Reg val to uA */
+ do_div(tmp, rsens_ohm);
+
+ pwr->curr_factor = tmp;
+ pwr->rsens = rsens_ohm;
+ dev_dbg(pwr->dev, "Setting rsens to %u ohm\n", pwr->rsens);
+ dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor);
+ return 0;
+}
+
+/*
+ * BD71827 has no proper support for detecting relaxed battery.
+ * Driver has implemented current polling and logic has been that:
+ * if for the specified time current consumption has always been below
+ * threshold value when polled - then battery is assumed to be relaxed. This
+ * for sure leads to a problem when current cunsumption has had short
+ * 'spikes' - but this is what the logic has been - and it has probably been
+ * working as the driver is left as is? So let's just keep this logic here.
+ */
+static bool bd71827_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ int tmp_curr_mA, ret, curr, curr_avg;
+
+ ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to get current\n");
+ return false;
+ }
+
+ tmp_curr_mA = uAMP_TO_mAMP(curr);
+ if ((tmp_curr_mA * tmp_curr_mA) <=
+ (THR_RELAX_CURRENT_DEFAULT * THR_RELAX_CURRENT_DEFAULT))
+ /* No load */
+ pwr->relax_time = pwr->relax_time + (JITTER_DEFAULT / 1000);
+ else
+ pwr->relax_time = 0;
+ if (!(pwr->relax_time >= THR_RELAX_TIME_DEFAULT))
+ return false;
+
+ ret = bd71827_get_voltage(sw, rex_volt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to get Vbat\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool bd71828_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+ int ret;
+ u16 tmp;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_rex_avg,
+ BD7182x_MASK_VBAT_U, &tmp);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read battery relax voltage\n");
+ return 0;
+ }
+ *rex_volt = tmp * 1000;
+
+ return !!tmp;
+}
+
+static int bd71828_get_cycle(struct simple_gauge *sw, int *cycle)
+{
+ int tmpret, ret, update = 0;
+ uint16_t charged_coulomb_cnt;
+ int cc_designed_cap;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->coulomb_chg3, 0xff,
+ &charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read charging CC (%d)\n", ret);
+ return ret;
+ }
+ dev_dbg(pwr->dev, "charged_coulomb_cnt = 0x%x\n",
+ (int)charged_coulomb_cnt);
+
+ cc_designed_cap = UAH_to_CC(pwr, sw->designed_cap);
+
+ while (charged_coulomb_cnt >= cc_designed_cap) {
+ update = 1;
+ /*
+ * sw-gauge caches old cycle value so we do not need to care
+ * about it. We just add new cycles
+ */
+ *cycle = *cycle + 1;
+ dev_dbg(pwr->dev, "Update cycle = %d\n", *cycle);
+ charged_coulomb_cnt -= cc_designed_cap;
+ }
+ if (update) {
+ ret = stop_cc(pwr);
+ if (ret)
+ return ret;
+
+ ret = bd7182x_write16(pwr, pwr->regs->coulomb_chg3,
+ charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to update charging CC (%d)\n",
+ ret);
+ }
+
+ tmpret = start_cc(pwr);
+ if (tmpret)
+ return tmpret;
+ }
+ return ret;
+}
+
+static void fgauge_initial_values(struct bd71827_power *pwr)
+{
+ struct simple_gauge_desc *d = &pwr->gdesc;
+ struct simple_gauge_ops *o = &pwr->ops;
+ bool use_vdr = false;
+
+ /* TODO: See if these could be get from DT? */
+ d->poll_interval = JITTER_DEFAULT; /* 3 seconds */
+ d->allow_set_cycle = true;
+ d->cap_adjust_volt_threshold = pwr->low_thr_voltage;
+ d->designed_cap = pwr->battery_cap;
+ d->clamp_soc = true;
+
+ o->get_uah_from_full = bd71828_get_uah_from_full; /* Ok */
+ o->get_uah = bd71828_get_uah; /* Ok */
+ o->update_cc_uah = bd71828_set_uah; /* Ok */
+ o->get_cycle = bd71828_get_cycle; /* Ok */
+ o->get_vsys = bd71827_get_vsys_min;
+
+ /*
+ * We have custom OCV table => provide our own volt_to_cap and
+ * ocv_by_soc which utilize the custom tables.
+ */
+ if (ocv_table[0]) {
+ dev_dbg(pwr->dev, "OCV values given as parameters\n");
+ o->get_soc_by_ocv = &bd71827_voltage_to_capacity;
+ o->get_ocv_by_soc = &bd71827_get_ocv;
+ }
+
+ if (vdr_table_h[0] && vdr_table_m[0] && vdr_table_l[0] &&
+ vdr_table_vl[0])
+ use_vdr = true;
+
+ /* TODO:
+ * o->suspend_calibrate = bd71828_suspend_calibrate;
+ */
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71828:
+ o->get_temp = bd71828_get_temp;
+ o->is_relaxed = bd71828_is_relaxed;
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ case ROHM_CHIP_TYPE_BD71827:
+ o->get_temp = bd71827_get_temp;
+ o->is_relaxed = bd71827_is_relaxed;
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ o->get_temp = bd71827_get_temp;
+ o->is_relaxed = bd71828_is_relaxed;
+ /*
+ * TODO: BD71815 has not been used with VDR. This is untested
+ * but I don't see why it wouldn't work by setting thresholds
+ * and by populating correct SOC-OCV tables.
+ */
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ /*
+ * No need to handle default here as this is done already in probe.
+ * But this keeps gcc shut-up.
+ */
+ default:
+ break;
+ }
+}
+
+static int bd71827_power_probe(struct platform_device *pdev)
+{
+ struct bd71827_power *pwr;
+ struct power_supply_config ac_cfg = {};
+ struct simple_gauge_psy psycfg;
+ int ret;
+ struct regmap *regmap;
+
+ psycfg = gauge_psy_config;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap) {
+ dev_err(&pdev->dev, "No parent regmap\n");
+ return -EINVAL;
+ }
+
+ pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL);
+ if (!pwr)
+ return -ENOMEM;
+
+ pwr->regmap = regmap;
+ pwr->dev = &pdev->dev;
+ pwr->chip_type = platform_get_device_id(pdev)->driver_data;
+
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71828:
+ pwr->bat_inserted = bd71828_bat_inserted;
+ pwr->regs = &pwr_regs_bd71828;
+ dev_dbg(pwr->dev, "Found ROHM BD71828\n");
+ psycfg.psy_name = "bd71828-charger";
+ break;
+ case ROHM_CHIP_TYPE_BD71827:
+ pwr->bat_inserted = bd71828_bat_inserted;
+ pwr->regs = &pwr_regs_bd71827;
+ dev_dbg(pwr->dev, "Found ROHM BD71817\n");
+ psycfg.psy_name = "bd71827-charger";
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ pwr->bat_inserted = bd71815_bat_inserted;
+ pwr->regs = &pwr_regs_bd71815;
+ psycfg.psy_name = "bd71815-charger";
+ dev_dbg(pwr->dev, "Found ROHM BD71815\n");
+ break;
+ default:
+ dev_err(pwr->dev, "Unknown PMIC\n");
+ return -EINVAL;
+ }
+
+ /* We need to set batcap etc before we do set fgauge initial values */
+ ret = bd71827_set_battery_parameters(pwr);
+ if (ret) {
+ dev_err(pwr->dev, "Missing battery parameters\n");
+
+ return ret;
+ }
+ fgauge_initial_values(pwr);
+
+ pwr->gdesc.drv_data = pwr;
+
+ ret = bd7182x_get_rsens(pwr);
+ if (ret) {
+ dev_err(&pdev->dev, "sense resistor missing\n");
+ return ret;
+ }
+
+ dev_set_drvdata(&pdev->dev, pwr);
+ bd71827_init_hardware(pwr);
+
+ psycfg.attr_grp = &bd71827_sysfs_attr_groups[0];
+ psycfg.of_node = pdev->dev.parent->of_node;
+
+ ac_cfg.supplied_to = bd71827_ac_supplied_to;
+ ac_cfg.num_supplicants = ARRAY_SIZE(bd71827_ac_supplied_to);
+ ac_cfg.drv_data = pwr;
+
+ pwr->ac = devm_power_supply_register(&pdev->dev, &bd71827_ac_desc,
+ &ac_cfg);
+ if (IS_ERR(pwr->ac)) {
+ ret = PTR_ERR(pwr->ac);
+ dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+ return ret;
+ }
+
+ pwr->sw = devm_psy_register_simple_gauge(pwr->dev, &psycfg, &pwr->ops,
+ &pwr->gdesc);
+ if (IS_ERR(pwr->sw)) {
+ dev_err(&pdev->dev, "SW-gauge registration failed\n");
+ return PTR_ERR(pwr->sw);
+ }
+
+ ret = bd7182x_get_irqs(pdev, pwr);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request IRQs: %d\n", ret);
+ return ret;
+ };
+
+ /* Configure wakeup capable */
+ device_set_wakeup_capable(pwr->dev, 1);
+ device_set_wakeup_enable(pwr->dev, 1);
+
+ return 0;
+}
+
+static const struct platform_device_id bd71827_charger_id[] = {
+ { "bd71815-power", ROHM_CHIP_TYPE_BD71815 },
+ { "bd71827-power", ROHM_CHIP_TYPE_BD71827 },
+ { "bd71828-power", ROHM_CHIP_TYPE_BD71828 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, bd71827_charger_id);
+
+static struct platform_driver bd71827_power_driver = {
+ .driver = {
+ .name = "bd718xx-power",
+ },
+ .probe = bd71827_power_probe,
+ .id_table = bd71827_charger_id,
+};
+
+module_platform_driver(bd71827_power_driver);
+MODULE_ALIAS("platform:bd718xx-power");
+
+module_param(use_load_bat_params, int, 0444);
+MODULE_PARM_DESC(use_load_bat_params, "use_load_bat_params:Use loading battery parameters");
+
+module_param(param_max_voltage, int, 0444);
+MODULE_PARM_DESC(param_max_voltage,
+ "Maximum voltage of fully charged battery, uV");
+
+module_param(param_min_voltage, int, 0444);
+MODULE_PARM_DESC(param_min_voltage,
+ "Minimum voltage of fully drained battery, uV");
+
+module_param(param_thr_voltage, int, 0444);
+MODULE_PARM_DESC(param_thr_voltage,
+ "Threshold voltage for applying zero correction, uV");
+
+module_param(battery_cap_mah, int, 0444);
+MODULE_PARM_DESC(battery_cap_mah, "battery_cap_mah:Battery capacity (mAh)");
+
+module_param(dgrd_cyc_cap, int, 0444);
+MODULE_PARM_DESC(dgrd_cyc_cap, "dgrd_cyc_cap:Degraded capacity per cycle (uAh)");
+
+module_param(soc_est_max_num, int, 0444);
+MODULE_PARM_DESC(soc_est_max_num, "soc_est_max_num:SOC estimation max repeat number");
+
+module_param_array(ocv_table, int, NULL, 0444);
+MODULE_PARM_DESC(ocv_table, "ocv_table:Open Circuit Voltage table (uV)");
+
+module_param_array(vdr_temps, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_temps, "vdr_temps:temperatures for VDR tables. (0.1C)");
+
+module_param_array(vdr_table_h, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_h, "vdr_table_h:Voltage Drop Ratio temperature high area table");
+
+module_param_array(vdr_table_m, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_m, "vdr_table_m:Voltage Drop Ratio temperature middle area table");
+
+module_param_array(vdr_table_l, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_l, "vdr_table_l:Voltage Drop Ratio temperature low area table");
+
+module_param_array(vdr_table_vl, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_vl, "vdr_table_vl:Voltage Drop Ratio temperature very low area table");
+
+MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>");
+MODULE_DESCRIPTION("ROHM BD718(15/17/27/28/78) PMIC Battery Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/rohm-bd71827.h b/include/linux/mfd/rohm-bd71827.h
new file mode 100644
index 000000000000..0f5a343b10ae
--- /dev/null
+++ b/include/linux/mfd/rohm-bd71827.h
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2016
+ *
+ * @author cpham2403@gmail.com
+ */
+
+#ifndef __LINUX_MFD_BD71827_H
+#define __LINUX_MFD_BD71827_H
+
+#include <linux/regmap.h>
+
+#define LDO5VSEL_EQ_H 0
+
+#ifndef LDO5VSEL_EQ_H
+ #error define LDO5VSEL_EQ_H to 1 when connect to High, to 0 when connect to Low
+#else
+ #if LDO5VSEL_EQ_H == 1
+ #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_H
+ #define LDO5_MASK LDO5_H_MASK
+ #elif LDO5VSEL_EQ_H == 0
+ #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_L
+ #define LDO5_MASK LDO5_L_MASK
+ #else
+ #error Define LDO5VSEL_EQ_H only to 0 or 1
+ #endif
+#endif
+
+enum {
+ BD71827_BUCK1 = 0,
+ BD71827_BUCK2,
+ BD71827_BUCK3,
+ BD71827_BUCK4,
+ BD71827_BUCK5,
+ // General Purpose
+ BD71827_LDO1,
+ BD71827_LDO2,
+ BD71827_LDO3,
+ BD71827_LDO4,
+ BD71827_LDO5,
+ BD71827_LDO6,
+ // LDO for Secure Non-Volatile Storage
+ BD71827_LDOSNVS,
+ BD71827_REGULATOR_CNT,
+};
+
+#define BD71827_SUPPLY_STATE_ENABLED 0x1
+
+#define BD71827_BUCK1_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK2_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK3_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK4_VOLTAGE_NUM 0x1F
+#define BD71827_BUCK5_VOLTAGE_NUM 0x1F
+#define BD71827_LDO1_VOLTAGE_NUM 0x3F
+#define BD71827_LDO2_VOLTAGE_NUM 0x3F
+#define BD71827_LDO3_VOLTAGE_NUM 0x3F
+#define BD71827_LDO4_VOLTAGE_NUM 0x3F
+#define BD71827_LDO5_VOLTAGE_NUM 0x3F
+#define BD71827_LDO6_VOLTAGE_NUM 0x1
+#define BD71827_LDOSNVS_VOLTAGE_NUM 0x1
+
+#define BD71827_GPIO_NUM 2 /* BD71827 have 2 GPO */
+
+enum {
+ BD71827_REG_DEVICE = 0x00,
+ BD71827_REG_PWRCTRL = 0x01,
+ BD71827_REG_BUCK1_MODE = 0x02,
+ BD71827_REG_BUCK2_MODE = 0x03,
+ BD71827_REG_BUCK3_MODE = 0x04,
+ BD71827_REG_BUCK4_MODE = 0x05,
+ BD71827_REG_BUCK5_MODE = 0x06,
+ BD71827_REG_BUCK1_VOLT_RUN = 0x07,
+ BD71827_REG_BUCK1_VOLT_SUSP = 0x08,
+ BD71827_REG_BUCK2_VOLT_RUN = 0x09,
+ BD71827_REG_BUCK2_VOLT_SUSP = 0x0A,
+ BD71827_REG_BUCK3_VOLT = 0x0B,
+ BD71827_REG_BUCK4_VOLT = 0x0C,
+ BD71827_REG_BUCK5_VOLT = 0x0D,
+ BD71827_REG_LED_CTRL = 0x0E,
+ BD71827_REG_reserved_0F = 0x0F,
+ BD71827_REG_LDO_MODE1 = 0x10,
+ BD71827_REG_LDO_MODE2 = 0x11,
+ BD71827_REG_LDO_MODE3 = 0x12,
+ BD71827_REG_LDO_MODE4 = 0x13,
+ BD71827_REG_LDO1_VOLT = 0x14,
+ BD71827_REG_LDO2_VOLT = 0x15,
+ BD71827_REG_LDO3_VOLT = 0x16,
+ BD71827_REG_LDO4_VOLT = 0x17,
+ BD71827_REG_LDO5_VOLT_H = 0x18,
+ BD71827_REG_LDO5_VOLT_L = 0x19,
+ BD71827_REG_BUCK_PD_DIS = 0x1A,
+ BD71827_REG_LDO_PD_DIS = 0x1B,
+ BD71827_REG_GPIO = 0x1C,
+ BD71827_REG_OUT32K = 0x1D,
+ BD71827_REG_SEC = 0x1E,
+ BD71827_REG_MIN = 0x1F,
+ BD71827_REG_HOUR = 0x20,
+ BD71827_REG_WEEK = 0x21,
+ BD71827_REG_DAY = 0x22,
+ BD71827_REG_MONTH = 0x23,
+ BD71827_REG_YEAR = 0x24,
+ BD71827_REG_ALM0_SEC = 0x25,
+ BD71827_REG_ALM0_MIN = 0x26,
+ BD71827_REG_ALM0_HOUR = 0x27,
+ BD71827_REG_ALM0_WEEK = 0x28,
+ BD71827_REG_ALM0_DAY = 0x29,
+ BD71827_REG_ALM0_MONTH = 0x2A,
+ BD71827_REG_ALM0_YEAR = 0x2B,
+ BD71827_REG_ALM1_SEC = 0x2C,
+ BD71827_REG_ALM1_MIN = 0x2D,
+ BD71827_REG_ALM1_HOUR = 0x2E,
+ BD71827_REG_ALM1_WEEK = 0x2F,
+ BD71827_REG_ALM1_DAY = 0x30,
+ BD71827_REG_ALM1_MONTH = 0x31,
+ BD71827_REG_ALM1_YEAR = 0x32,
+ BD71827_REG_ALM0_MASK = 0x33,
+ BD71827_REG_ALM1_MASK = 0x34,
+ BD71827_REG_ALM2 = 0x35,
+ BD71827_REG_TRIM = 0x36,
+ BD71827_REG_CONF = 0x37,
+ BD71827_REG_SYS_INIT = 0x38,
+ BD71827_REG_CHG_STATE = 0x39,
+ BD71827_REG_CHG_LAST_STATE = 0x3A,
+ BD71827_REG_BAT_STAT = 0x3B,
+ BD71827_REG_DCIN_STAT = 0x3C,
+ BD71827_REG_VSYS_STAT = 0x3D,
+ BD71827_REG_CHG_STAT = 0x3E,
+ BD71827_REG_CHG_WDT_STAT = 0x3F,
+ BD71827_REG_BAT_TEMP = 0x40,
+ BD71827_REG_ILIM_STAT = 0x41,
+ BD71827_REG_DCIN_SET = 0x42,
+ BD71827_REG_DCIN_CLPS = 0x43,
+ BD71827_REG_VSYS_REG = 0x44,
+ BD71827_REG_VSYS_MAX = 0x45,
+ BD71827_REG_VSYS_MIN = 0x46,
+ BD71827_REG_CHG_SET1 = 0x47,
+ BD71827_REG_CHG_SET2 = 0x48,
+ BD71827_REG_CHG_WDT_PRE = 0x49,
+ BD71827_REG_CHG_WDT_FST = 0x4A,
+ BD71827_REG_CHG_IPRE = 0x4B,
+ BD71827_REG_CHG_IFST = 0x4C,
+ BD71827_REG_CHG_IFST_TERM = 0x4D,
+ BD71827_REG_CHG_VPRE = 0x4E,
+ BD71827_REG_CHG_VBAT_1 = 0x4F,
+ BD71827_REG_CHG_VBAT_2 = 0x50,
+ BD71827_REG_CHG_VBAT_3 = 0x51,
+ BD71827_REG_CHG_LED_1 = 0x52,
+ BD71827_REG_VF_TH = 0x53,
+ BD71827_REG_BAT_SET_1 = 0x54,
+ BD71827_REG_BAT_SET_2 = 0x55,
+ BD71827_REG_BAT_SET_3 = 0x56,
+ BD71827_REG_ALM_VBAT_TH_U = 0x57,
+ BD71827_REG_ALM_VBAT_TH_L = 0x58,
+ BD71827_REG_ALM_DCIN_TH = 0x59,
+ BD71827_REG_ALM_VSYS_TH = 0x5A,
+ BD71827_REG_reserved_5B = 0x5B,
+ BD71827_REG_reserved_5C = 0x5C,
+ BD71827_REG_VM_VBAT_U = 0x5D,
+ BD71827_REG_VM_VBAT_L = 0x5E,
+ BD71827_REG_VM_BTMP = 0x5F,
+ BD71827_REG_VM_VTH = 0x60,
+ BD71827_REG_VM_DCIN_U = 0x61,
+ BD71827_REG_VM_DCIN_L = 0x62,
+ BD71827_REG_reserved_63 = 0x63,
+ BD71827_REG_VM_VF = 0x64,
+ BD71827_REG_reserved_65 = 0x65,
+ BD71827_REG_reserved_66 = 0x66,
+ BD71827_REG_VM_OCV_PRE_U = 0x67,
+ BD71827_REG_VM_OCV_PRE_L = 0x68,
+ BD71827_REG_reserved_69 = 0x69,
+ BD71827_REG_reserved_6A = 0x6A,
+ BD71827_REG_VM_OCV_PST_U = 0x6B,
+ BD71827_REG_VM_OCV_PST_L = 0x6C,
+ BD71827_REG_VM_SA_VBAT_U = 0x6D,
+ BD71827_REG_VM_SA_VBAT_L = 0x6E,
+ BD71827_REG_reserved_6F = 0x6F,
+ BD71827_REG_reserved_70 = 0x70,
+ BD71827_REG_CC_CTRL = 0x71,
+ BD71827_REG_CC_BATCAP1_TH_U = 0x72,
+ BD71827_REG_CC_BATCAP1_TH_L = 0x73,
+ BD71827_REG_CC_BATCAP2_TH_U = 0x74,
+ BD71827_REG_CC_BATCAP2_TH_L = 0x75,
+ BD71827_REG_CC_BATCAP3_TH_U = 0x76,
+ BD71827_REG_CC_BATCAP3_TH_L = 0x77,
+ BD71827_REG_CC_STAT = 0x78,
+ BD71827_REG_CC_CCNTD_3 = 0x79,
+ BD71827_REG_CC_CCNTD_2 = 0x7A,
+ BD71827_REG_CC_CCNTD_1 = 0x7B,
+ BD71827_REG_CC_CCNTD_0 = 0x7C,
+ BD71827_REG_CC_CURCD_U = 0x7D,
+ BD71827_REG_CC_CURCD_L = 0x7E,
+ BD71827_REG_CC_OCUR_THR_1 = 0x7F,
+ BD71827_REG_CC_OCUR_DUR_1 = 0x80,
+ BD71827_REG_CC_OCUR_THR_2 = 0x81,
+ BD71827_REG_CC_OCUR_DUR_2 = 0x82,
+ BD71827_REG_CC_OCUR_THR_3 = 0x83,
+ BD71827_REG_CC_OCUR_DUR_3 = 0x84,
+ BD71827_REG_CC_OCUR_MON = 0x85,
+ BD71827_REG_VM_BTMP_OV_THR = 0x86,
+ BD71827_REG_VM_BTMP_OV_DUR = 0x87,
+ BD71827_REG_VM_BTMP_LO_THR = 0x88,
+ BD71827_REG_VM_BTMP_LO_DUR = 0x89,
+ BD71827_REG_VM_BTMP_MON = 0x8A,
+ BD71827_REG_INT_EN_01 = 0x8B,
+ BD71827_REG_INT_EN_02 = 0x8C,
+ BD71827_REG_INT_EN_03 = 0x8D,
+ BD71827_REG_INT_EN_04 = 0x8E,
+ BD71827_REG_INT_EN_05 = 0x8F,
+ BD71827_REG_INT_EN_06 = 0x90,
+ BD71827_REG_INT_EN_07 = 0x91,
+ BD71827_REG_INT_EN_08 = 0x92,
+ BD71827_REG_INT_EN_09 = 0x93,
+ BD71827_REG_INT_EN_10 = 0x94,
+ BD71827_REG_INT_EN_11 = 0x95,
+ BD71827_REG_INT_EN_12 = 0x96,
+ BD71827_REG_INT_STAT = 0x97,
+ BD71827_REG_INT_STAT_01 = 0x98,
+ BD71827_REG_INT_STAT_02 = 0x99,
+ BD71827_REG_INT_STAT_03 = 0x9A,
+ BD71827_REG_INT_STAT_04 = 0x9B,
+ BD71827_REG_INT_STAT_05 = 0x9C,
+ BD71827_REG_INT_STAT_06 = 0x9D,
+ BD71827_REG_INT_STAT_07 = 0x9E,
+ BD71827_REG_INT_STAT_08 = 0x9F,
+ BD71827_REG_INT_STAT_09 = 0xA0,
+ BD71827_REG_INT_STAT_10 = 0xA1,
+ BD71827_REG_INT_STAT_11 = 0xA2,
+ BD71827_REG_INT_STAT_12 = 0xA3,
+ BD71827_REG_INT_UPDATE = 0xA4,
+ BD71827_REG_PWRCTRL2 = 0xA8,
+ BD71827_REG_PWRCTRL3 = 0xA9,
+ BD71827_REG_SWRESET = 0xAA,
+ BD71827_REG_BUCK1_VOLT_IDLE = 0xAB,
+ BD71827_REG_BUCK2_VOLT_IDLE = 0xAC,
+ BD71827_REG_ONEVNT_MODE_1 = 0xAD,
+ BD71827_REG_ONEVNT_MODE_2 = 0xAE,
+ BD71827_REG_RESERVE_0 = 0xB0,
+ BD71827_REG_RESERVE_1 = 0xB1,
+ BD71827_REG_RESERVE_2 = 0xB2,
+ BD71827_REG_RESERVE_3 = 0xB3,
+ BD71827_REG_RESERVE_4 = 0xB4,
+ BD71827_REG_RESERVE_5 = 0xB5,
+ BD71827_REG_RESERVE_6 = 0xB6,
+ BD71827_REG_RESERVE_7 = 0xB7,
+ BD71827_REG_RESERVE_8 = 0xB8,
+ BD71827_REG_RESERVE_9 = 0xB9,
+ BD71827_REG_RESERVE_A = 0xBA,
+ BD71827_REG_RESERVE_B = 0xBB,
+ BD71827_REG_RESERVE_C = 0xBC,
+ BD71827_REG_RESERVE_D = 0xBD,
+ BD71827_REG_RESERVE_E = 0xBE,
+ BD71827_REG_RESERVE_F = 0xBF,
+ BD71827_REG_VM_VSYS_U = 0xC0,
+ BD71827_REG_VM_VSYS_L = 0xC1,
+ BD71827_REG_VM_SA_VSYS_U = 0xC2,
+ BD71827_REG_VM_SA_VSYS_L = 0xC3,
+ BD71827_REG_CC_SA_CURCD_U = 0xC4,
+ BD71827_REG_CC_SA_CURCD_L = 0xC5,
+ BD71827_REG_BATID = 0xC6,
+ BD71827_REG_VM_SA_VBAT_MIN_U = 0xD4,
+ BD71827_REG_VM_SA_VBAT_MIN_L = 0xD5,
+ BD71827_REG_VM_SA_VBAT_MAX_U = 0xD6,
+ BD71827_REG_VM_SA_VBAT_MAX_L = 0xD7,
+ BD71827_REG_VM_SA_VSYS_MIN_U = 0xD8,
+ BD71827_REG_VM_SA_VSYS_MIN_L = 0xD9,
+ BD71827_REG_VM_SA_VSYS_MAX_U = 0xDA,
+ BD71827_REG_VM_SA_VSYS_MAX_L = 0xDB,
+ BD71827_REG_VM_SA_MINMAX_CLR = 0xDC,
+ BD71827_REG_VM_OCV_PWRON_U = 0xDD,
+ BD71827_REG_VM_OCV_PWRON_L = 0xDE,
+ BD71827_REG_REX_CCNTD_3 = 0xE0,
+ BD71827_REG_REX_CCNTD_2 = 0xE1,
+ BD71827_REG_REX_CCNTD_1 = 0xE2,
+ BD71827_REG_REX_CCNTD_0 = 0xE3,
+ BD71827_REG_REX_SA_VBAT_U = 0xE4,
+ BD71827_REG_REX_SA_VBAT_L = 0xE5,
+ BD71827_REG_REX_CTRL_1 = 0xE6,
+ BD71827_REG_REX_CTRL_2 = 0xE7,
+ BD71827_REG_FULL_CCNTD_3 = 0xE8,
+ BD71827_REG_FULL_CCNTD_2 = 0xE9,
+ BD71827_REG_FULL_CCNTD_1 = 0xEA,
+ BD71827_REG_FULL_CCNTD_0 = 0xEB,
+ BD71827_REG_FULL_CTRL = 0xEC,
+ BD71827_REG_CCNTD_CHG_3 = 0xF0,
+ BD71827_REG_CCNTD_CHG_2 = 0xF1,
+ BD71827_REG_INT_EN_13 = 0xF8,
+ BD71827_REG_INT_STAT_13 = 0xF9,
+ BD71827_REG_I2C_MAGIC = 0xFE,
+ BD71827_REG_PRODUCT = 0xFF,
+ BD71827_MAX_REGISTER = 0x100,
+};
+
+#define BD71827_REX_CLR_MASK 0x10
+
+#endif /* __LINUX_MFD_BD71827_H */
--
2.31.1
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related [flat|nested] 34+ messages in thread