summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/supply/88pm860x_charger.c8
-rw-r--r--drivers/power/supply/Kconfig16
-rw-r--r--drivers/power/supply/Makefile2
-rw-r--r--drivers/power/supply/ab8500_btemp.c3
-rw-r--r--drivers/power/supply/adc-battery-helper.c327
-rw-r--r--drivers/power/supply/adc-battery-helper.h62
-rw-r--r--drivers/power/supply/bq2415x_charger.c4
-rw-r--r--drivers/power/supply/bq24190_charger.c2
-rw-r--r--drivers/power/supply/bq27xxx_battery.c17
-rw-r--r--drivers/power/supply/cw2015_battery.c8
-rw-r--r--drivers/power/supply/gpio-charger.c7
-rw-r--r--drivers/power/supply/intel_dc_ti_battery.c389
-rw-r--r--drivers/power/supply/ipaq_micro_battery.c3
-rw-r--r--drivers/power/supply/max77705_charger.c332
-rw-r--r--drivers/power/supply/max77976_charger.c12
-rw-r--r--drivers/power/supply/mt6370-charger.c18
-rw-r--r--drivers/power/supply/power_supply_sysfs.c2
-rw-r--r--drivers/power/supply/qcom_battmgr.c324
-rw-r--r--drivers/power/supply/rk817_charger.c6
-rw-r--r--drivers/power/supply/rt9467-charger.c47
-rw-r--r--drivers/power/supply/rx51_battery.c2
-rw-r--r--drivers/power/supply/sbs-charger.c16
-rw-r--r--drivers/power/supply/sbs-manager.c2
-rw-r--r--drivers/power/supply/ucs1002_power.c2
-rw-r--r--drivers/power/supply/ug3105_battery.c346
25 files changed, 1444 insertions, 513 deletions
diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c
index 2b9fcb7e71d7..8d99c6ff72ed 100644
--- a/drivers/power/supply/88pm860x_charger.c
+++ b/drivers/power/supply/88pm860x_charger.c
@@ -284,8 +284,8 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
{
struct power_supply *psy;
union power_supply_propval data;
- unsigned char fsm_state[][16] = { "init", "discharge", "precharge",
- "fastcharge",
+ static const unsigned char fsm_state[][16] = {
+ "init", "discharge", "precharge", "fastcharge",
};
int ret;
int vbatt;
@@ -313,7 +313,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, "
"Allowed:%d\n",
- &fsm_state[info->state][0],
+ fsm_state[info->state],
(info->online) ? "online" : "N/A",
(info->present) ? "present" : "N/A", info->allowed);
dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt);
@@ -385,7 +385,7 @@ static int set_charging_fsm(struct pm860x_charger_info *info)
}
dev_dbg(info->dev,
"Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n",
- &fsm_state[info->state][0],
+ fsm_state[info->state],
(info->online) ? "online" : "N/A",
(info->present) ? "present" : "N/A", info->allowed);
mutex_unlock(&info->lock);
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 11893c50c5d2..dca4be23ee70 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -35,6 +35,9 @@ config APM_POWER
Say Y here to enable support APM status emulation using
battery class devices.
+config ADC_BATTERY_HELPER
+ tristate
+
config GENERIC_ADC_BATTERY
tristate "Generic battery support using IIO"
depends on IIO
@@ -244,6 +247,18 @@ config BATTERY_INGENIC
This driver can also be built as a module. If so, the module will be
called ingenic-battery.
+config BATTERY_INTEL_DC_TI
+ tristate "Intel Bay / Cherry Trail Dollar Cove TI battery driver"
+ depends on INTEL_SOC_PMIC_CHTDC_TI && INTEL_DC_TI_ADC && IIO && ACPI
+ select ADC_BATTERY_HELPER
+ help
+ Choose this option if you want to monitor battery status on Intel
+ Bay Trail / Cherry Trail tablets using the Dollar Cove TI PMIC's
+ coulomb-counter as fuel-gauge.
+
+ To compile this driver as a module, choose M here: the module will be
+ called intel_dc_ti_battery.
+
config BATTERY_IPAQ_MICRO
tristate "iPAQ Atmel Micro ASIC battery driver"
depends on MFD_IPAQ_MICRO
@@ -1050,6 +1065,7 @@ config CHARGER_SURFACE
config BATTERY_UG3105
tristate "uPI uG3105 battery monitor driver"
depends on I2C
+ select ADC_BATTERY_HELPER
help
Battery monitor driver for the uPI uG3105 battery monitor.
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index a7d91244bb82..99a820d38197 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -7,6 +7,7 @@ power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
+obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
obj-$(CONFIG_APM_POWER) += apm_power.o
@@ -41,6 +42,7 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
+obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index b00c84fbc33c..e5202a7b6209 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -667,7 +667,8 @@ static int ab8500_btemp_bind(struct device *dev, struct device *master,
/* Create a work queue for the btemp */
di->btemp_wq =
- alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0);
+ alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM | WQ_PERCPU,
+ 0);
if (di->btemp_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c
new file mode 100644
index 000000000000..6e0f5b6d73d7
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ *
+ * Some fuel-gauges are not full-featured autonomous fuel-gauges.
+ * These fuel-gauges offer accurate current and voltage measurements but
+ * their coulomb-counters are intended to work together with an always on
+ * micro-controller monitoring the fuel-gauge.
+ *
+ * This adc-battery-helper code offers open-circuit-voltage (ocv) and through
+ * that capacity estimation for devices where such limited functionality
+ * fuel-gauges are exposed directly to Linux.
+ *
+ * This helper requires the hw to provide accurate battery current_now and
+ * voltage_now measurement and this helper the provides the following properties
+ * based on top of those readings:
+ *
+ * POWER_SUPPLY_PROP_STATUS
+ * POWER_SUPPLY_PROP_VOLTAGE_OCV
+ * POWER_SUPPLY_PROP_VOLTAGE_NOW
+ * POWER_SUPPLY_PROP_CURRENT_NOW
+ * POWER_SUPPLY_PROP_CAPACITY
+ *
+ * As well as optional the following properties assuming an always present
+ * system-scope battery, allowing direct use of adc_battery_helper_get_prop()
+ * in this common case:
+ * POWER_SUPPLY_PROP_PRESENT
+ * POWER_SUPPLY_PROP_SCOPE
+ *
+ * Using this helper is as simple as:
+ *
+ * 1. Embed a struct adc_battery_helper this MUST be the first member of
+ * the battery driver's data struct.
+ * 2. Use adc_battery_helper_props[] or add the above properties to
+ * the list of properties in power_supply_desc
+ * 3. Call adc_battery_helper_init() after registering the power_supply and
+ * before returning from the probe() function
+ * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property()
+ * method, or call it for the above properties.
+ * 5. Use adc_battery_helper_external_power_changed() as the power-supply's
+ * external_power_changed() method or call it from that method.
+ * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or
+ * call them from the driver's suspend-resume methods.
+ *
+ * The provided get_voltage_and_current_now() method will be called by this
+ * helper at adc_battery_helper_init() time and later.
+ *
+ * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/devm-helpers.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+
+#include "adc-battery-helper.h"
+
+#define MOV_AVG_WINDOW_SIZE ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE
+#define INIT_POLL_TIME (5 * HZ)
+#define POLL_TIME (30 * HZ)
+#define SETTLE_TIME (1 * HZ)
+
+#define INIT_POLL_COUNT 30
+
+#define CURR_HYST_UA 65000
+
+#define LOW_BAT_UV 3700000
+#define FULL_BAT_HYST_UV 38000
+
+#define AMBIENT_TEMP_CELSIUS 25
+
+static int adc_battery_helper_get_status(struct adc_battery_helper *help)
+{
+ int full_uv =
+ help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV;
+
+ if (help->curr_ua > CURR_HYST_UA)
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ if (help->curr_ua < -CURR_HYST_UA)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ if (help->supplied) {
+ bool full;
+
+ if (help->charge_finished)
+ full = gpiod_get_value_cansleep(help->charge_finished);
+ else
+ full = help->ocv_avg_uv > full_uv;
+
+ if (full)
+ return POWER_SUPPLY_STATUS_FULL;
+ }
+
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static void adc_battery_helper_work(struct work_struct *work)
+{
+ struct adc_battery_helper *help = container_of(work, struct adc_battery_helper,
+ work.work);
+ int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size;
+ struct device *dev = help->psy->dev.parent;
+ int volt_uv, prev_volt_uv = help->volt_uv;
+ int curr_ua, prev_curr_ua = help->curr_ua;
+ bool prev_supplied = help->supplied;
+ int prev_status = help->status;
+
+ guard(mutex)(&help->lock);
+
+ ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua);
+ if (ret)
+ goto out;
+
+ help->volt_uv = volt_uv;
+ help->curr_ua = curr_ua;
+
+ help->ocv_uv[help->ocv_avg_index] =
+ help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000;
+ dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n",
+ help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]);
+ help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
+ help->poll_count++;
+
+ help->ocv_avg_uv = 0;
+ win_size = min(help->poll_count, MOV_AVG_WINDOW_SIZE);
+ for (i = 0; i < win_size; i++)
+ help->ocv_avg_uv += help->ocv_uv[i];
+ help->ocv_avg_uv /= win_size;
+
+ help->supplied = power_supply_am_i_supplied(help->psy);
+ help->status = adc_battery_helper_get_status(help);
+ if (help->status == POWER_SUPPLY_STATUS_FULL)
+ help->capacity = 100;
+ else
+ help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info,
+ help->ocv_avg_uv,
+ AMBIENT_TEMP_CELSIUS);
+
+ /*
+ * Skip internal resistance calc on charger [un]plug and
+ * when the battery is almost empty (voltage low).
+ */
+ if (help->supplied != prev_supplied ||
+ help->volt_uv < LOW_BAT_UV ||
+ help->poll_count < 2)
+ goto out;
+
+ /*
+ * Assuming that the OCV voltage does not change significantly
+ * between 2 polls, then we can calculate the internal resistance
+ * on a significant current change by attributing all voltage
+ * change between the 2 readings to the internal resistance.
+ */
+ curr_diff_ua = abs(help->curr_ua - prev_curr_ua);
+ if (curr_diff_ua < CURR_HYST_UA)
+ goto out;
+
+ volt_diff_uv = abs(help->volt_uv - prev_volt_uv);
+ res_mohm = volt_diff_uv * 1000 / curr_diff_ua;
+
+ if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) ||
+ (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) {
+ dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm);
+ goto out;
+ }
+
+ dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm);
+
+ help->intern_res_mohm[help->intern_res_avg_index] = res_mohm;
+ help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
+ help->intern_res_poll_count++;
+
+ help->intern_res_avg_mohm = 0;
+ win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW_SIZE);
+ for (i = 0; i < win_size; i++)
+ help->intern_res_avg_mohm += help->intern_res_mohm[i];
+ help->intern_res_avg_mohm /= win_size;
+
+out:
+ queue_delayed_work(system_percpu_wq, &help->work,
+ (help->poll_count <= INIT_POLL_COUNT) ?
+ INIT_POLL_TIME : POLL_TIME);
+
+ if (help->status != prev_status)
+ power_supply_changed(help->psy);
+}
+
+const enum power_supply_property adc_battery_helper_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+EXPORT_SYMBOL_GPL(adc_battery_helper_properties);
+
+static_assert(ARRAY_SIZE(adc_battery_helper_properties) ==
+ ADC_HELPER_NUM_PROPERTIES);
+
+int adc_battery_helper_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+ int dummy, ret = 0;
+
+ /*
+ * Avoid racing with adc_battery_helper_work() while it is updating
+ * variables and avoid calling get_voltage_and_current_now() reentrantly.
+ */
+ guard(mutex)(&help->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = help->status;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = help->ocv_avg_uv;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = help->capacity;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_get_property);
+
+void adc_battery_helper_external_power_changed(struct power_supply *psy)
+{
+ struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+
+ dev_dbg(help->psy->dev.parent, "external power changed\n");
+ mod_delayed_work(system_percpu_wq, &help->work, SETTLE_TIME);
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed);
+
+static void adc_battery_helper_start_work(struct adc_battery_helper *help)
+{
+ help->poll_count = 0;
+ help->ocv_avg_index = 0;
+
+ queue_delayed_work(system_percpu_wq, &help->work, 0);
+ flush_delayed_work(&help->work);
+}
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+ adc_battery_helper_get_func get_voltage_and_current_now,
+ struct gpio_desc *charge_finished_gpio)
+{
+ struct device *dev = psy->dev.parent;
+ int ret;
+
+ help->psy = psy;
+ help->get_voltage_and_current_now = get_voltage_and_current_now;
+ help->charge_finished = charge_finished_gpio;
+
+ ret = devm_mutex_init(dev, &help->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work);
+ if (ret)
+ return ret;
+
+ if (!help->psy->battery_info ||
+ help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
+ help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
+ !psy->battery_info->ocv_table[0]) {
+ dev_err(dev, "error required properties are missing\n");
+ return -ENODEV;
+ }
+
+ /* Use provided internal resistance as start point (in milli-ohm) */
+ help->intern_res_avg_mohm =
+ help->psy->battery_info->factory_internal_resistance_uohm / 1000;
+ /* Also add it to the internal resistance moving average window */
+ help->intern_res_mohm[0] = help->intern_res_avg_mohm;
+ help->intern_res_avg_index = 1;
+ help->intern_res_poll_count = 1;
+
+ adc_battery_helper_start_work(help);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_init);
+
+int adc_battery_helper_suspend(struct device *dev)
+{
+ struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&help->work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_suspend);
+
+int adc_battery_helper_resume(struct device *dev)
+{
+ struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+ adc_battery_helper_start_work(help);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_resume);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("ADC battery capacity estimation helper");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h
new file mode 100644
index 000000000000..4e42181c8983
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#define ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE 8
+
+struct power_supply;
+struct gpio_desc;
+
+/*
+ * The adc battery helper code needs voltage- and current-now to be sampled as
+ * close to each other (in sample-time) as possible. A single getter function is
+ * used to allow the battery driver to handle this in the best way possible.
+ */
+typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, int *curr);
+
+struct adc_battery_helper {
+ struct power_supply *psy;
+ struct gpio_desc *charge_finished;
+ struct delayed_work work;
+ struct mutex lock;
+ adc_battery_helper_get_func get_voltage_and_current_now;
+ int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* micro-volt */
+ int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE]; /* milli-ohm */
+ int poll_count;
+ int ocv_avg_index;
+ int ocv_avg_uv; /* micro-volt */
+ int intern_res_poll_count;
+ int intern_res_avg_index;
+ int intern_res_avg_mohm; /* milli-ohm */
+ int volt_uv; /* micro-volt */
+ int curr_ua; /* micro-ampere */
+ int capacity; /* percent */
+ int status;
+ bool supplied;
+};
+
+extern const enum power_supply_property adc_battery_helper_properties[];
+/* Must be const cannot be an external. Asserted in adc-battery-helper.c */
+#define ADC_HELPER_NUM_PROPERTIES 7
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+ adc_battery_helper_get_func get_voltage_and_current_now,
+ struct gpio_desc *charge_finished_gpio);
+/*
+ * The below functions can be directly used as power-supply / suspend-resume
+ * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data
+ * directly to struct adc_battery_helper. Therefor struct adc_battery_helper
+ * MUST be the first member of the battery driver's data struct.
+ */
+int adc_battery_helper_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+void adc_battery_helper_external_power_changed(struct power_supply *psy);
+int adc_battery_helper_suspend(struct device *dev);
+int adc_battery_helper_resume(struct device *dev);
diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c
index 917c26ee56bc..b50a28b9dd38 100644
--- a/drivers/power/supply/bq2415x_charger.c
+++ b/drivers/power/supply/bq2415x_charger.c
@@ -842,7 +842,7 @@ static int bq2415x_notifier_call(struct notifier_block *nb,
if (bq->automode < 1)
return NOTIFY_OK;
- mod_delayed_work(system_wq, &bq->work, 0);
+ mod_delayed_work(system_percpu_wq, &bq->work, 0);
return NOTIFY_OK;
}
@@ -1516,7 +1516,7 @@ static int bq2415x_power_supply_init(struct bq2415x_device *bq)
ret = bq2415x_detect_revision(bq);
if (ret < 0)
- strcpy(revstr, "unknown");
+ strscpy(revstr, "unknown", sizeof(revstr));
else
sprintf(revstr, "1.%d", ret);
diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c
index e1510c7fdab3..ed0ceae8d90b 100644
--- a/drivers/power/supply/bq24190_charger.c
+++ b/drivers/power/supply/bq24190_charger.c
@@ -1467,7 +1467,7 @@ static void bq24190_charger_external_power_changed(struct power_supply *psy)
* too low default 500mA iinlim. Delay setting the input-current-limit
* for 300ms to avoid this.
*/
- queue_delayed_work(system_wq, &bdi->input_current_limit_work,
+ queue_delayed_work(system_percpu_wq, &bdi->input_current_limit_work,
msecs_to_jiffies(300));
}
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index ad2d9ecf32a5..19445e39651c 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -1127,7 +1127,7 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k
mutex_lock(&bq27xxx_list_lock);
list_for_each_entry(di, &bq27xxx_battery_devices, list)
- mod_delayed_work(system_wq, &di->work, 0);
+ mod_delayed_work(system_percpu_wq, &di->work, 0);
mutex_unlock(&bq27xxx_list_lock);
return ret;
@@ -1945,7 +1945,7 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di)
di->last_update = jiffies;
if (!di->removed && poll_interval > 0)
- mod_delayed_work(system_wq, &di->work, poll_interval * HZ);
+ mod_delayed_work(system_percpu_wq, &di->work, poll_interval * HZ);
}
void bq27xxx_battery_update(struct bq27xxx_device_info *di)
@@ -2221,14 +2221,7 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
struct bq27xxx_device_info *di = power_supply_get_drvdata(psy);
/* After charger plug in/out wait 0.5s for things to stabilize */
- mod_delayed_work(system_wq, &di->work, HZ / 2);
-}
-
-static void bq27xxx_battery_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
+ mod_delayed_work(system_percpu_wq, &di->work, HZ / 2);
}
int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
@@ -2242,9 +2235,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
int ret;
INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
- mutex_init(&di->lock);
- ret = devm_add_action_or_reset(di->dev, bq27xxx_battery_mutex_destroy,
- &di->lock);
+ ret = devm_mutex_init(di->dev, &di->lock);
if (ret)
return ret;
diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c
index f63c3c410451..2263d5d3448f 100644
--- a/drivers/power/supply/cw2015_battery.c
+++ b/drivers/power/supply/cw2015_battery.c
@@ -506,10 +506,7 @@ static int cw_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
- if (cw_bat->battery->charge_full_design_uah > 0)
- val->intval = cw_bat->battery->charge_full_design_uah;
- else
- val->intval = 0;
+ val->intval = max(cw_bat->battery->charge_full_design_uah, 0);
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
@@ -702,8 +699,7 @@ static int cw_bat_probe(struct i2c_client *client)
if (!cw_bat->battery_workqueue)
return -ENOMEM;
- devm_delayed_work_autocancel(&client->dev,
- &cw_bat->battery_delay_work, cw_bat_work);
+ devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work);
queue_delayed_work(cw_bat->battery_workqueue,
&cw_bat->battery_delay_work, msecs_to_jiffies(10));
return 0;
diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c
index 1b2da9b5fb65..2504190eba82 100644
--- a/drivers/power/supply/gpio-charger.c
+++ b/drivers/power/supply/gpio-charger.c
@@ -79,7 +79,8 @@ static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val)
for (i = 0; i < ndescs; i++) {
bool val = (mapping.gpiodata >> i) & 1;
- gpiod_set_value_cansleep(gpios[ndescs-i-1], val);
+
+ gpiod_set_value_cansleep(gpios[ndescs - i - 1], val);
}
gpio_charger->charge_current_limit = mapping.limit_ua;
@@ -226,14 +227,14 @@ static int init_charge_current_limit(struct device *dev,
gpio_charger->current_limit_map_size = len / 2;
len = device_property_read_u32_array(dev, "charge-current-limit-mapping",
- (u32*) gpio_charger->current_limit_map, len);
+ (u32 *) gpio_charger->current_limit_map, len);
if (len < 0)
return len;
set_def_limit = !device_property_read_u32(dev,
"charge-current-limit-default-microamp",
&def_limit);
- for (i=0; i < gpio_charger->current_limit_map_size; i++) {
+ for (i = 0; i < gpio_charger->current_limit_map_size; i++) {
if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) {
dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n");
return -EINVAL;
diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c
new file mode 100644
index 000000000000..56b0c92e9d28
--- /dev/null
+++ b/drivers/power/supply/intel_dc_ti_battery.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC
+ *
+ * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured
+ * autonomous fuel-gauge. It is intended to work together with an always on
+ * micro-controller monitoring it.
+ *
+ * Since Linux does not monitor coulomb-counter changes while the device
+ * is off or suspended, voltage based capacity estimation from
+ * the adc-battery-helper code is used.
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ *
+ * Register definitions and calibration code was taken from
+ * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel
+ * which has the following copyright header:
+ *
+ * Copyright (C) 2014 Intel Corporation
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ *
+ * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive
+ * named: "App. Guide_Acer_20151221_A_A.zip"
+ * which is distributed by Acer from the Acer A1-840 support page:
+ * https://www.acer.com/us-en/support/product-support/A1-840/downloads
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/consumer.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/timekeeping.h>
+
+#include "adc-battery-helper.h"
+
+#define DC_TI_PMIC_VERSION_REG 0x00
+#define PMIC_VERSION_A0 0xC0
+#define PMIC_VERSION_A1 0xC1
+
+#define DC_TI_CC_CNTL_REG 0x60
+#define CC_CNTL_CC_CTR_EN BIT(0)
+#define CC_CNTL_CC_CLR_EN BIT(1)
+#define CC_CNTL_CC_CAL_EN BIT(2)
+#define CC_CNTL_CC_OFFSET_EN BIT(3)
+#define CC_CNTL_SMPL_INTVL GENMASK(5, 4)
+#define CC_CNTL_SMPL_INTVL_15MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 0)
+#define CC_CNTL_SMPL_INTVL_62MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 1)
+#define CC_CNTL_SMPL_INTVL_125MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 2)
+#define CC_CNTL_SMPL_INTVL_250MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 3)
+
+#define DC_TI_SMPL_CTR0_REG 0x69
+#define DC_TI_SMPL_CTR1_REG 0x68
+#define DC_TI_SMPL_CTR2_REG 0x67
+
+#define DC_TI_CC_OFFSET_HI_REG 0x61
+#define CC_OFFSET_HI_MASK 0x3F
+#define DC_TI_CC_OFFSET_LO_REG 0x62
+
+#define DC_TI_SW_OFFSET_REG 0x6C
+
+#define DC_TI_CC_ACC3_REG 0x63
+#define DC_TI_CC_ACC2_REG 0x64
+#define DC_TI_CC_ACC1_REG 0x65
+#define DC_TI_CC_ACC0_REG 0x66
+
+#define DC_TI_CC_INTG1_REG 0x6A
+#define DC_TI_CC_INTG1_MASK 0x3F
+#define DC_TI_CC_INTG0_REG 0x6B
+
+#define DC_TI_EEPROM_ACCESS_CONTROL 0x88
+#define EEPROM_UNLOCK 0xDA
+#define EEPROM_LOCK 0x00
+
+#define DC_TI_EEPROM_CC_GAIN_REG 0xF4
+#define CC_TRIM_REVISION GENMASK(3, 0)
+#define CC_GAIN_CORRECTION GENMASK(7, 4)
+
+#define PMIC_VERSION_A0_TRIM_REV 3
+#define PMIC_VERSION_A1_MIN_TRIM_REV 1
+
+#define DC_TI_EEPROM_CC_OFFSET_REG 0xFD
+
+#define DC_TI_EEPROM_CTRL 0xFE
+#define EEPROM_BANK0_SEL 0x01
+#define EEPROM_BANK1_SEL 0x02
+
+#define SMPL_INTVL_US 15000
+#define SMPL_INTVL_MS (SMPL_INTVL_US / USEC_PER_MSEC)
+#define CALIBRATION_TIME_US (10 * SMPL_INTVL_US)
+#define SLEEP_SLACK_US 2500
+
+/* CC gain correction is in 0.0025 increments */
+#define CC_GAIN_STEP 25
+#define CC_GAIN_DIV 10000
+
+/* CC offset is in 0.5 units per 250ms (default sample interval) */
+#define CC_OFFSET_DIV 2
+#define CC_OFFSET_SMPL_INTVL_MS 250
+
+/* CC accumulator scale is 366.2 ųCoulumb / unit */
+#define CC_ACC_TO_UA(acc, smpl_ctr) \
+ ((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS))
+
+#define DEV_NAME "chtdc_ti_battery"
+
+struct dc_ti_battery_chip {
+ /* Must be the first member see adc-battery-helper documentation */
+ struct adc_battery_helper helper;
+ struct device *dev;
+ struct regmap *regmap;
+ struct iio_channel *vbat_channel;
+ struct power_supply *psy;
+ int cc_gain;
+ int cc_offset;
+};
+
+static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
+{
+ struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy);
+ s64 cnt_start_usec, now_usec, sleep_usec;
+ unsigned int reg_val;
+ s32 acc, smpl_ctr;
+ int ret;
+
+ /*
+ * Enable coulomb-counter before reading Vbat from ADC, so that the CC
+ * samples are from the same time period as the Vbat reading.
+ */
+ ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+ CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN);
+ if (ret)
+ goto out_err;
+
+ cnt_start_usec = ktime_get_ns() / NSEC_PER_USEC;
+
+ /* Read Vbat, convert IIO mV to power-supply ųV */
+ ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000);
+ if (ret < 0)
+ goto out_err;
+
+ /* Sleep at least 3 sample-times + slack to get 3+ CC samples */
+ now_usec = ktime_get_ns() / NSEC_PER_USEC;
+ sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - (now_usec - cnt_start_usec);
+ if (sleep_usec > 0 && sleep_usec < 1000000)
+ usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US);
+
+ /*
+ * The PMIC latches the coulomb- and sample-counters upon reading the
+ * CC_ACC0 register. Reading multiple registers at once is not supported.
+ *
+ * Step 1: Read CC_ACC0 - CC_ACC3
+ */
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc = reg_val;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc |= reg_val << 8;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc |= reg_val << 16;
+
+ ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ acc |= reg_val << 24;
+
+ /* Step 2: Read SMPL_CTR0 - SMPL_CTR2 */
+ ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ smpl_ctr = reg_val;
+
+ ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ smpl_ctr |= reg_val << 8;
+
+ ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, &reg_val);
+ if (ret)
+ goto out_err;
+
+ smpl_ctr |= reg_val << 16;
+
+ /* Disable the coulumb-counter again */
+ ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+ CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
+ if (ret)
+ goto out_err;
+
+ /* Apply calibration */
+ acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS /
+ (CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV);
+ acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV;
+ *curr = CC_ACC_TO_UA(acc, smpl_ctr);
+
+ return 0;
+
+out_err:
+ dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret);
+ return ret;
+}
+
+static const struct power_supply_desc dc_ti_battery_psy_desc = {
+ .name = "intel_dc_ti_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = adc_battery_helper_get_property,
+ .external_power_changed = adc_battery_helper_external_power_changed,
+ .properties = adc_battery_helper_properties,
+ .num_properties = ADC_HELPER_NUM_PROPERTIES,
+};
+
+static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip)
+{
+ u8 pmic_version, cc_trim_rev;
+ unsigned int reg_val;
+ int ret;
+
+ /* Set sample rate to 15 ms and calibrate the coulomb-counter */
+ ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+ CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN |
+ CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN);
+ if (ret)
+ goto out;
+
+ fsleep(CALIBRATION_TIME_US);
+
+ /* Disable coulomb-counter it is only used while getting the current */
+ ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+ CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, &reg_val);
+ if (ret)
+ goto out;
+
+ pmic_version = reg_val;
+
+ /*
+ * As per the PMIC vendor (TI), the calibration offset and gain err
+ * values are stored in EEPROM Bank 0 and Bank 1 of the PMIC.
+ * We need to read the stored offset and gain margins and need
+ * to apply the corrections to the raw coulomb counter value.
+ */
+
+ /* Unlock the EEPROM Access */
+ ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK);
+ if (ret)
+ goto out;
+
+ /* Select Bank 1 to read CC GAIN Err correction */
+ ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, &reg_val);
+ if (ret)
+ goto out;
+
+ cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val);
+
+ dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d\n", pmic_version, cc_trim_rev);
+
+ if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) &&
+ !(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) {
+ dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n");
+ goto out_relock;
+ }
+
+ chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val);
+
+ /* Select Bank 0 to read CC OFFSET Correction */
+ ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL);
+ if (ret)
+ goto out_relock;
+
+ ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, &reg_val);
+ if (ret)
+ goto out_relock;
+
+ chip->cc_offset = (s8)reg_val;
+
+ dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain);
+
+out_relock:
+ /* Re-lock the EEPROM Access */
+ regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
+out:
+ if (ret)
+ dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
+
+ return ret;
+}
+
+static int dc_ti_battery_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct power_supply_config psy_cfg = {};
+ struct fwnode_reference_args args;
+ struct gpio_desc *charge_finished;
+ struct dc_ti_battery_chip *chip;
+ int ret;
+
+ /* On most devices with a Dollar Cove TI the battery is handled by ACPI */
+ if (!acpi_quirk_skip_acpi_ac_and_battery())
+ return -ENODEV;
+
+ /* ACPI glue code adds a "monitored-battery" fwnode, wait for this */
+ ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery",
+ NULL, 0, 0, &args);
+ if (ret) {
+ dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret);
+ return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n");
+ }
+
+ fwnode_handle_put(args.fwnode);
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ chip->regmap = pmic->regmap;
+
+ chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
+ if (IS_ERR(chip->vbat_channel)) {
+ dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
+ return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
+ }
+
+ charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN);
+ if (IS_ERR(charge_finished))
+ return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n");
+
+ ret = dc_ti_battery_hw_init(chip);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, chip);
+
+ psy_cfg.drv_data = chip;
+ chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg);
+ if (IS_ERR(chip->psy))
+ return PTR_ERR(chip->psy);
+
+ return adc_battery_helper_init(&chip->helper, chip->psy,
+ dc_ti_battery_get_voltage_and_current_now,
+ charge_finished);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
+ adc_battery_helper_resume, NULL);
+
+static struct platform_driver dc_ti_battery_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ .pm = pm_sleep_ptr(&dc_ti_battery_pm_ops),
+ },
+ .probe = dc_ti_battery_probe,
+};
+module_platform_driver(dc_ti_battery_driver);
+
+MODULE_ALIAS("platform:" DEV_NAME);
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c
index 7e0568a5353f..ff8573a5ca6d 100644
--- a/drivers/power/supply/ipaq_micro_battery.c
+++ b/drivers/power/supply/ipaq_micro_battery.c
@@ -232,7 +232,8 @@ static int micro_batt_probe(struct platform_device *pdev)
return -ENOMEM;
mb->micro = dev_get_drvdata(pdev->dev.parent);
- mb->wq = alloc_workqueue("ipaq-battery-wq", WQ_MEM_RECLAIM, 0);
+ mb->wq = alloc_workqueue("ipaq-battery-wq",
+ WQ_MEM_RECLAIM | WQ_PERCPU, 0);
if (!mb->wq)
return -ENOMEM;
diff --git a/drivers/power/supply/max77705_charger.c b/drivers/power/supply/max77705_charger.c
index 329b430d0e50..b1a227bf72e2 100644
--- a/drivers/power/supply/max77705_charger.c
+++ b/drivers/power/supply/max77705_charger.c
@@ -40,31 +40,30 @@ static enum power_supply_property max77705_charger_props[] = {
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
};
-static int max77705_chgin_irq(void *irq_drv_data)
+static irqreturn_t max77705_chgin_irq(int irq, void *irq_drv_data)
{
- struct max77705_charger_data *charger = irq_drv_data;
+ struct max77705_charger_data *chg = irq_drv_data;
- queue_work(charger->wqueue, &charger->chgin_work);
+ queue_work(chg->wqueue, &chg->chgin_work);
- return 0;
+ return IRQ_HANDLED;
}
static const struct regmap_irq max77705_charger_irqs[] = {
- { .mask = MAX77705_BYP_IM, },
- { .mask = MAX77705_INP_LIMIT_IM, },
- { .mask = MAX77705_BATP_IM, },
- { .mask = MAX77705_BAT_IM, },
- { .mask = MAX77705_CHG_IM, },
- { .mask = MAX77705_WCIN_IM, },
- { .mask = MAX77705_CHGIN_IM, },
- { .mask = MAX77705_AICL_IM, },
+ REGMAP_IRQ_REG_LINE(MAX77705_BYP_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_INP_LIMIT_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_BATP_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_BAT_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_CHG_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_WCIN_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_CHGIN_I, BITS_PER_BYTE),
+ REGMAP_IRQ_REG_LINE(MAX77705_AICL_I, BITS_PER_BYTE),
};
static struct regmap_irq_chip max77705_charger_irq_chip = {
.name = "max77705-charger",
.status_base = MAX77705_CHG_REG_INT,
.mask_base = MAX77705_CHG_REG_INT_MASK,
- .handle_post_irq = max77705_chgin_irq,
.num_regs = 1,
.irqs = max77705_charger_irqs,
.num_irqs = ARRAY_SIZE(max77705_charger_irqs),
@@ -74,8 +73,7 @@ static int max77705_charger_enable(struct max77705_charger_data *chg)
{
int rv;
- rv = regmap_update_bits(chg->regmap, MAX77705_CHG_REG_CNFG_09,
- MAX77705_CHG_EN_MASK, MAX77705_CHG_EN_MASK);
+ rv = regmap_field_write(chg->rfield[MAX77705_CHG_EN], 1);
if (rv)
dev_err(chg->dev, "unable to enable the charger: %d\n", rv);
@@ -87,10 +85,7 @@ static void max77705_charger_disable(void *data)
struct max77705_charger_data *chg = data;
int rv;
- rv = regmap_update_bits(chg->regmap,
- MAX77705_CHG_REG_CNFG_09,
- MAX77705_CHG_EN_MASK,
- MAX77705_CHG_DISABLE);
+ rv = regmap_field_write(chg->rfield[MAX77705_CHG_EN], MAX77705_CHG_DISABLE);
if (rv)
dev_err(chg->dev, "unable to disable the charger: %d\n", rv);
}
@@ -109,19 +104,30 @@ static int max77705_get_online(struct regmap *regmap, int *val)
return 0;
}
-static int max77705_check_battery(struct max77705_charger_data *charger, int *val)
+static int max77705_set_integer(struct max77705_charger_data *chg, enum max77705_field_idx fidx,
+ unsigned int clamp_min, unsigned int clamp_max,
+ unsigned int div, int val)
+{
+ unsigned int regval;
+
+ regval = clamp_val(val, clamp_min, clamp_max) / div;
+
+ return regmap_field_write(chg->rfield[fidx], regval);
+}
+
+static int max77705_check_battery(struct max77705_charger_data *chg, int *val)
{
unsigned int reg_data;
unsigned int reg_data2;
- struct regmap *regmap = charger->regmap;
+ struct regmap *regmap = chg->regmap;
regmap_read(regmap, MAX77705_CHG_REG_INT_OK, &reg_data);
- dev_dbg(charger->dev, "CHG_INT_OK(0x%x)\n", reg_data);
+ dev_dbg(chg->dev, "CHG_INT_OK(0x%x)\n", reg_data);
regmap_read(regmap, MAX77705_CHG_REG_DETAILS_00, &reg_data2);
- dev_dbg(charger->dev, "CHG_DETAILS00(0x%x)\n", reg_data2);
+ dev_dbg(chg->dev, "CHG_DETAILS00(0x%x)\n", reg_data2);
if ((reg_data & MAX77705_BATP_OK) || !(reg_data2 & MAX77705_BATP_DTLS))
*val = true;
@@ -131,13 +137,13 @@ static int max77705_check_battery(struct max77705_charger_data *charger, int *va
return 0;
}
-static int max77705_get_charge_type(struct max77705_charger_data *charger, int *val)
+static int max77705_get_charge_type(struct max77705_charger_data *chg, int *val)
{
- struct regmap *regmap = charger->regmap;
- unsigned int reg_data;
+ struct regmap *regmap = chg->regmap;
+ unsigned int reg_data, chg_en;
- regmap_read(regmap, MAX77705_CHG_REG_CNFG_09, &reg_data);
- if (!MAX77705_CHARGER_CHG_CHARGING(reg_data)) {
+ regmap_field_read(chg->rfield[MAX77705_CHG_EN], &chg_en);
+ if (!chg_en) {
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
return 0;
}
@@ -159,13 +165,13 @@ static int max77705_get_charge_type(struct max77705_charger_data *charger, int *
return 0;
}
-static int max77705_get_status(struct max77705_charger_data *charger, int *val)
+static int max77705_get_status(struct max77705_charger_data *chg, int *val)
{
- struct regmap *regmap = charger->regmap;
- unsigned int reg_data;
+ struct regmap *regmap = chg->regmap;
+ unsigned int reg_data, chg_en;
- regmap_read(regmap, MAX77705_CHG_REG_CNFG_09, &reg_data);
- if (!MAX77705_CHARGER_CHG_CHARGING(reg_data)) {
+ regmap_field_read(chg->rfield[MAX77705_CHG_EN], &chg_en);
+ if (!chg_en) {
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
return 0;
}
@@ -234,10 +240,10 @@ static int max77705_get_vbus_state(struct regmap *regmap, int *value)
return 0;
}
-static int max77705_get_battery_health(struct max77705_charger_data *charger,
+static int max77705_get_battery_health(struct max77705_charger_data *chg,
int *value)
{
- struct regmap *regmap = charger->regmap;
+ struct regmap *regmap = chg->regmap;
unsigned int bat_dtls;
regmap_read(regmap, MAX77705_CHG_REG_DETAILS_01, &bat_dtls);
@@ -245,16 +251,16 @@ static int max77705_get_battery_health(struct max77705_charger_data *charger,
switch (bat_dtls) {
case MAX77705_BATTERY_NOBAT:
- dev_dbg(charger->dev, "%s: No battery and the charger is suspended\n",
+ dev_dbg(chg->dev, "%s: No battery and the chg is suspended\n",
__func__);
*value = POWER_SUPPLY_HEALTH_NO_BATTERY;
break;
case MAX77705_BATTERY_PREQUALIFICATION:
- dev_dbg(charger->dev, "%s: battery is okay but its voltage is low(~VPQLB)\n",
+ dev_dbg(chg->dev, "%s: battery is okay but its voltage is low(~VPQLB)\n",
__func__);
break;
case MAX77705_BATTERY_DEAD:
- dev_dbg(charger->dev, "%s: battery dead\n", __func__);
+ dev_dbg(chg->dev, "%s: battery dead\n", __func__);
*value = POWER_SUPPLY_HEALTH_DEAD;
break;
case MAX77705_BATTERY_GOOD:
@@ -262,11 +268,11 @@ static int max77705_get_battery_health(struct max77705_charger_data *charger,
*value = POWER_SUPPLY_HEALTH_GOOD;
break;
case MAX77705_BATTERY_OVERVOLTAGE:
- dev_dbg(charger->dev, "%s: battery ovp\n", __func__);
+ dev_dbg(chg->dev, "%s: battery ovp\n", __func__);
*value = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
break;
default:
- dev_dbg(charger->dev, "%s: battery unknown\n", __func__);
+ dev_dbg(chg->dev, "%s: battery unknown\n", __func__);
*value = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
}
@@ -274,9 +280,9 @@ static int max77705_get_battery_health(struct max77705_charger_data *charger,
return 0;
}
-static int max77705_get_health(struct max77705_charger_data *charger, int *val)
+static int max77705_get_health(struct max77705_charger_data *chg, int *val)
{
- struct regmap *regmap = charger->regmap;
+ struct regmap *regmap = chg->regmap;
int ret, is_online = 0;
ret = max77705_get_online(regmap, &is_online);
@@ -287,24 +293,19 @@ static int max77705_get_health(struct max77705_charger_data *charger, int *val)
if (ret || (*val != POWER_SUPPLY_HEALTH_GOOD))
return ret;
}
- return max77705_get_battery_health(charger, val);
+ return max77705_get_battery_health(chg, val);
}
-static int max77705_get_input_current(struct max77705_charger_data *charger,
+static int max77705_get_input_current(struct max77705_charger_data *chg,
int *val)
{
unsigned int reg_data;
int get_current = 0;
- struct regmap *regmap = charger->regmap;
-
- regmap_read(regmap, MAX77705_CHG_REG_CNFG_09, &reg_data);
- reg_data &= MAX77705_CHG_CHGIN_LIM_MASK;
+ regmap_field_read(chg->rfield[MAX77705_CHG_CHGIN_LIM], &reg_data);
if (reg_data <= 3)
get_current = MAX77705_CURRENT_CHGIN_MIN;
- else if (reg_data >= MAX77705_CHG_CHGIN_LIM_MASK)
- get_current = MAX77705_CURRENT_CHGIN_MAX;
else
get_current = (reg_data + 1) * MAX77705_CURRENT_CHGIN_STEP;
@@ -313,26 +314,23 @@ static int max77705_get_input_current(struct max77705_charger_data *charger,
return 0;
}
-static int max77705_get_charge_current(struct max77705_charger_data *charger,
+static int max77705_get_charge_current(struct max77705_charger_data *chg,
int *val)
{
unsigned int reg_data;
- struct regmap *regmap = charger->regmap;
- regmap_read(regmap, MAX77705_CHG_REG_CNFG_02, &reg_data);
- reg_data &= MAX77705_CHG_CC;
+ regmap_field_read(chg->rfield[MAX77705_CHG_CC_LIM], &reg_data);
*val = reg_data <= 0x2 ? MAX77705_CURRENT_CHGIN_MIN : reg_data * MAX77705_CURRENT_CHG_STEP;
return 0;
}
-static int max77705_set_float_voltage(struct max77705_charger_data *charger,
+static int max77705_set_float_voltage(struct max77705_charger_data *chg,
int float_voltage)
{
int float_voltage_mv;
unsigned int reg_data = 0;
- struct regmap *regmap = charger->regmap;
float_voltage_mv = float_voltage / 1000;
reg_data = float_voltage_mv <= 4000 ? 0x0 :
@@ -340,20 +338,16 @@ static int max77705_set_float_voltage(struct max77705_charger_data *charger,
(float_voltage_mv <= 4200) ? (float_voltage_mv - 4000) / 50 :
(((float_voltage_mv - 4200) / 10) + 0x04);
- return regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_04,
- MAX77705_CHG_CV_PRM_MASK,
- (reg_data << MAX77705_CHG_CV_PRM_SHIFT));
+ return regmap_field_write(chg->rfield[MAX77705_CHG_CV_PRM], reg_data);
}
-static int max77705_get_float_voltage(struct max77705_charger_data *charger,
+static int max77705_get_float_voltage(struct max77705_charger_data *chg,
int *val)
{
unsigned int reg_data = 0;
int voltage_mv;
- struct regmap *regmap = charger->regmap;
- regmap_read(regmap, MAX77705_CHG_REG_CNFG_04, &reg_data);
- reg_data &= MAX77705_CHG_PRM_MASK;
+ regmap_field_read(chg->rfield[MAX77705_CHG_CV_PRM], &reg_data);
voltage_mv = reg_data <= 0x04 ? reg_data * 50 + 4000 :
(reg_data - 4) * 10 + 4200;
*val = voltage_mv * 1000;
@@ -365,28 +359,28 @@ static int max77705_chg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
- struct max77705_charger_data *charger = power_supply_get_drvdata(psy);
- struct regmap *regmap = charger->regmap;
+ struct max77705_charger_data *chg = power_supply_get_drvdata(psy);
+ struct regmap *regmap = chg->regmap;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
return max77705_get_online(regmap, &val->intval);
case POWER_SUPPLY_PROP_PRESENT:
- return max77705_check_battery(charger, &val->intval);
+ return max77705_check_battery(chg, &val->intval);
case POWER_SUPPLY_PROP_STATUS:
- return max77705_get_status(charger, &val->intval);
+ return max77705_get_status(chg, &val->intval);
case POWER_SUPPLY_PROP_CHARGE_TYPE:
- return max77705_get_charge_type(charger, &val->intval);
+ return max77705_get_charge_type(chg, &val->intval);
case POWER_SUPPLY_PROP_HEALTH:
- return max77705_get_health(charger, &val->intval);
+ return max77705_get_health(chg, &val->intval);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- return max77705_get_input_current(charger, &val->intval);
+ return max77705_get_input_current(chg, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- return max77705_get_charge_current(charger, &val->intval);
+ return max77705_get_charge_current(chg, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
- return max77705_get_float_voltage(charger, &val->intval);
+ return max77705_get_float_voltage(chg, &val->intval);
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
- val->intval = charger->bat_info->voltage_max_design_uv;
+ val->intval = chg->bat_info->voltage_max_design_uv;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = max77705_charger_model;
@@ -400,74 +394,131 @@ static int max77705_chg_get_property(struct power_supply *psy,
return 0;
}
+static int max77705_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max77705_charger_data *chg = power_supply_get_drvdata(psy);
+ int err = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ err = max77705_set_integer(chg, MAX77705_CHG_CC_LIM,
+ MAX77705_CURRENT_CHGIN_MIN,
+ MAX77705_CURRENT_CHGIN_MAX,
+ MAX77705_CURRENT_CHG_STEP,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ err = max77705_set_integer(chg, MAX77705_CHG_CHGIN_LIM,
+ MAX77705_CURRENT_CHGIN_MIN,
+ MAX77705_CURRENT_CHGIN_MAX,
+ MAX77705_CURRENT_CHGIN_STEP,
+ val->intval);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+};
+
+static int max77705_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
static const struct power_supply_desc max77705_charger_psy_desc = {
.name = "max77705-charger",
- .type = POWER_SUPPLY_TYPE_USB,
+ .type = POWER_SUPPLY_TYPE_USB,
.properties = max77705_charger_props,
+ .property_is_writeable = max77705_property_is_writeable,
.num_properties = ARRAY_SIZE(max77705_charger_props),
.get_property = max77705_chg_get_property,
+ .set_property = max77705_set_property,
};
static void max77705_chgin_isr_work(struct work_struct *work)
{
- struct max77705_charger_data *charger =
+ struct max77705_charger_data *chg =
container_of(work, struct max77705_charger_data, chgin_work);
- power_supply_changed(charger->psy_chg);
+ power_supply_changed(chg->psy_chg);
}
-static void max77705_charger_initialize(struct max77705_charger_data *chg)
+static int max77705_charger_initialize(struct max77705_charger_data *chg)
{
- u8 reg_data;
struct power_supply_battery_info *info;
struct regmap *regmap = chg->regmap;
+ int err;
- if (power_supply_get_battery_info(chg->psy_chg, &info) < 0)
- return;
+ err = power_supply_get_battery_info(chg->psy_chg, &info);
+ if (err)
+ return dev_err_probe(chg->dev, err, "error on getting battery info");
chg->bat_info = info;
/* unlock charger setting protect */
/* slowest LX slope */
- reg_data = MAX77705_CHGPROT_MASK | MAX77705_SLOWEST_LX_SLOPE;
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_06, reg_data,
- reg_data);
+ err = regmap_field_write(chg->rfield[MAX77705_CHGPROT], MAX77705_CHGPROT_UNLOCKED);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_LX_SLOPE], MAX77705_SLOWEST_LX_SLOPE);
+ if (err)
+ goto err;
/* fast charge timer disable */
/* restart threshold disable */
/* pre-qual charge disable */
- reg_data = (MAX77705_FCHGTIME_DISABLE << MAX77705_FCHGTIME_SHIFT) |
- (MAX77705_CHG_RSTRT_DISABLE << MAX77705_CHG_RSTRT_SHIFT) |
- (MAX77705_CHG_PQEN_DISABLE << MAX77705_PQEN_SHIFT);
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_01,
- (MAX77705_FCHGTIME_MASK |
- MAX77705_CHG_RSTRT_MASK |
- MAX77705_PQEN_MASK),
- reg_data);
-
- /* OTG off(UNO on), boost off */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_00,
- MAX77705_OTG_CTRL, 0);
+ err = regmap_field_write(chg->rfield[MAX77705_FCHGTIME], MAX77705_FCHGTIME_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_CHG_RSTRT], MAX77705_CHG_RSTRT_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_CHG_PQEN], MAX77705_CHG_PQEN_DISABLE);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_MODE],
+ MAX77705_CHG_MASK | MAX77705_BUCK_MASK);
+ if (err)
+ goto err;
/* charge current 450mA(default) */
/* otg current limit 900mA */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_02,
- MAX77705_OTG_ILIM_MASK,
- MAX77705_OTG_ILIM_900 << MAX77705_OTG_ILIM_SHIFT);
+ err = regmap_field_write(chg->rfield[MAX77705_OTG_ILIM], MAX77705_OTG_ILIM_900);
+ if (err)
+ goto err;
/* BAT to SYS OCP 4.80A */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_05,
- MAX77705_REG_B2SOVRC_MASK,
- MAX77705_B2SOVRC_4_8A << MAX77705_REG_B2SOVRC_SHIFT);
+ err = regmap_field_write(chg->rfield[MAX77705_REG_B2SOVRC], MAX77705_B2SOVRC_4_8A);
+ if (err)
+ goto err;
+
/* top off current 150mA */
/* top off timer 30min */
- reg_data = (MAX77705_TO_ITH_150MA << MAX77705_TO_ITH_SHIFT) |
- (MAX77705_TO_TIME_30M << MAX77705_TO_TIME_SHIFT) |
- (MAX77705_SYS_TRACK_DISABLE << MAX77705_SYS_TRACK_DIS_SHIFT);
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_03,
- (MAX77705_TO_ITH_MASK |
- MAX77705_TO_TIME_MASK |
- MAX77705_SYS_TRACK_DIS_MASK), reg_data);
+ err = regmap_field_write(chg->rfield[MAX77705_TO], MAX77705_TO_ITH_150MA);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_TO_TIME], MAX77705_TO_TIME_30M);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_SYS_TRACK], MAX77705_SYS_TRACK_DISABLE);
+ if (err)
+ goto err;
/* cv voltage 4.2V or 4.35V */
/* MINVSYS 3.6V(default) */
@@ -478,28 +529,38 @@ static void max77705_charger_initialize(struct max77705_charger_data *chg)
max77705_set_float_voltage(chg, info->voltage_max_design_uv);
}
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_12,
- MAX77705_VCHGIN_REG_MASK, MAX77705_VCHGIN_4_5);
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_12,
- MAX77705_WCIN_REG_MASK, MAX77705_WCIN_4_5);
+ err = regmap_field_write(chg->rfield[MAX77705_VCHGIN], MAX77705_VCHGIN_4_5);
+ if (err)
+ goto err;
+
+ err = regmap_field_write(chg->rfield[MAX77705_WCIN], MAX77705_WCIN_4_5);
+ if (err)
+ goto err;
/* Watchdog timer */
regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_00,
MAX77705_WDTEN_MASK, 0);
- /* Active Discharge Enable */
- regmap_update_bits(regmap, MAX77705_PMIC_REG_MAINCTRL1, 1, 1);
-
/* VBYPSET=5.0V */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_11, MAX77705_VBYPSET_MASK, 0);
+ err = regmap_field_write(chg->rfield[MAX77705_VBYPSET], 0);
+ if (err)
+ goto err;
/* Switching Frequency : 1.5MHz */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_08, MAX77705_REG_FSW_MASK,
- (MAX77705_CHG_FSW_1_5MHz << MAX77705_REG_FSW_SHIFT));
+ err = regmap_field_write(chg->rfield[MAX77705_REG_FSW], MAX77705_CHG_FSW_1_5MHz);
+ if (err)
+ goto err;
/* Auto skip mode */
- regmap_update_bits(regmap, MAX77705_CHG_REG_CNFG_12, MAX77705_REG_DISKIP_MASK,
- (MAX77705_AUTO_SKIP << MAX77705_REG_DISKIP_SHIFT));
+ err = regmap_field_write(chg->rfield[MAX77705_REG_DISKIP], MAX77705_AUTO_SKIP);
+ if (err)
+ goto err;
+
+ return 0;
+
+err:
+ return dev_err_probe(chg->dev, err, "error while configuring");
+
}
static int max77705_charger_probe(struct i2c_client *i2c)
@@ -523,11 +584,13 @@ static int max77705_charger_probe(struct i2c_client *i2c)
if (IS_ERR(chg->regmap))
return PTR_ERR(chg->regmap);
- ret = regmap_update_bits(chg->regmap,
- MAX77705_CHG_REG_INT_MASK,
- MAX77705_CHGIN_IM, 0);
- if (ret)
- return ret;
+ for (int i = 0; i < MAX77705_N_REGMAP_FIELDS; i++) {
+ chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
+ max77705_reg_field[i]);
+ if (IS_ERR(chg->rfield[i]))
+ return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
+ "cannot allocate regmap field\n");
+ }
pscfg.fwnode = dev_fwnode(dev);
pscfg.drv_data = chg;
@@ -538,7 +601,7 @@ static int max77705_charger_probe(struct i2c_client *i2c)
max77705_charger_irq_chip.irq_drv_data = chg;
ret = devm_regmap_add_irq_chip(chg->dev, chg->regmap, i2c->irq,
- IRQF_ONESHOT | IRQF_SHARED, 0,
+ IRQF_ONESHOT, 0,
&max77705_charger_irq_chip,
&irq_data);
if (ret)
@@ -546,7 +609,7 @@ static int max77705_charger_probe(struct i2c_client *i2c)
chg->wqueue = create_singlethread_workqueue(dev_name(dev));
if (!chg->wqueue)
- return dev_err_probe(dev, -ENOMEM, "failed to create workqueue\n");
+ return -ENOMEM;
ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work);
if (ret) {
@@ -554,7 +617,20 @@ static int max77705_charger_probe(struct i2c_client *i2c)
goto destroy_wq;
}
- max77705_charger_initialize(chg);
+ ret = max77705_charger_initialize(chg);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to initialize charger IC\n");
+ goto destroy_wq;
+ }
+
+ ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_CHGIN_I),
+ NULL, max77705_chgin_irq,
+ IRQF_TRIGGER_NONE,
+ "chgin-irq", chg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to Request chgin IRQ\n");
+ goto destroy_wq;
+ }
ret = max77705_charger_enable(chg);
if (ret) {
diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c
index e6fe68cebc32..3d6ff4005533 100644
--- a/drivers/power/supply/max77976_charger.c
+++ b/drivers/power/supply/max77976_charger.c
@@ -292,10 +292,10 @@ static int max77976_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_ONLINE:
err = max77976_get_online(chg, &val->intval);
break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = MAX77976_CHG_CC_MAX;
break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
err = max77976_get_integer(chg, CHG_CC,
MAX77976_CHG_CC_MIN,
MAX77976_CHG_CC_MAX,
@@ -330,7 +330,7 @@ static int max77976_set_property(struct power_supply *psy,
int err = 0;
switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
err = max77976_set_integer(chg, CHG_CC,
MAX77976_CHG_CC_MIN,
MAX77976_CHG_CC_MAX,
@@ -355,7 +355,7 @@ static int max77976_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
@@ -368,8 +368,8 @@ static enum power_supply_property max77976_psy_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c
index 98579998b300..e6db961d5818 100644
--- a/drivers/power/supply/mt6370-charger.c
+++ b/drivers/power/supply/mt6370-charger.c
@@ -761,13 +761,6 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv)
return PTR_ERR_OR_ZERO(priv->psy);
}
-static void mt6370_chg_destroy_attach_lock(void *data)
-{
- struct mutex *attach_lock = data;
-
- mutex_destroy(attach_lock);
-}
-
static void mt6370_chg_destroy_wq(void *data)
{
struct workqueue_struct *wq = data;
@@ -894,22 +887,19 @@ static int mt6370_chg_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to init psy\n");
- mutex_init(&priv->attach_lock);
- ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_attach_lock,
- &priv->attach_lock);
+ ret = devm_mutex_init(dev, &priv->attach_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init attach lock\n");
+ return ret;
priv->attach = MT6370_ATTACH_STAT_DETACH;
priv->wq = create_singlethread_workqueue(dev_name(priv->dev));
if (!priv->wq)
- return dev_err_probe(dev, -ENOMEM,
- "Failed to create workqueue\n");
+ return -ENOMEM;
ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_wq, priv->wq);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init wq\n");
+ return ret;
ret = devm_work_autocancel(dev, &priv->bc12_work, mt6370_chg_bc12_work_func);
if (ret)
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 18e5e84a81c6..198405f7126f 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -223,6 +223,8 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
POWER_SUPPLY_ATTR(MANUFACTURE_YEAR),
POWER_SUPPLY_ATTR(MANUFACTURE_MONTH),
POWER_SUPPLY_ATTR(MANUFACTURE_DAY),
+ POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE),
+ POWER_SUPPLY_ATTR(STATE_OF_HEALTH),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(MODEL_NAME),
POWER_SUPPLY_ATTR(MANUFACTURER),
diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c
index 99808ea9851f..3c2837ef3461 100644
--- a/drivers/power/supply/qcom_battmgr.c
+++ b/drivers/power/supply/qcom_battmgr.c
@@ -2,10 +2,12 @@
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022, Linaro Ltd
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
#include <linux/of_device.h>
#include <linux/power_supply.h>
#include <linux/property.h>
@@ -18,8 +20,10 @@
#define BATTMGR_STRING_LEN 128
enum qcom_battmgr_variant {
- QCOM_BATTMGR_SM8350,
QCOM_BATTMGR_SC8280XP,
+ QCOM_BATTMGR_SM8350,
+ QCOM_BATTMGR_SM8550,
+ QCOM_BATTMGR_X1E80100,
};
#define BATTMGR_BAT_STATUS 0x1
@@ -30,8 +34,9 @@ enum qcom_battmgr_variant {
#define NOTIF_BAT_PROPERTY 0x30
#define NOTIF_USB_PROPERTY 0x32
#define NOTIF_WLS_PROPERTY 0x34
-#define NOTIF_BAT_INFO 0x81
#define NOTIF_BAT_STATUS 0x80
+#define NOTIF_BAT_INFO 0x81
+#define NOTIF_BAT_CHARGING_STATE 0x83
#define BATTMGR_BAT_INFO 0x9
@@ -65,6 +70,9 @@ enum qcom_battmgr_variant {
#define BATT_RESISTANCE 21
#define BATT_POWER_NOW 22
#define BATT_POWER_AVG 23
+#define BATT_CHG_CTRL_EN 24
+#define BATT_CHG_CTRL_START_THR 25
+#define BATT_CHG_CTRL_END_THR 26
#define BATTMGR_USB_PROPERTY_GET 0x32
#define BATTMGR_USB_PROPERTY_SET 0x33
@@ -89,6 +97,13 @@ enum qcom_battmgr_variant {
#define WLS_TYPE 5
#define WLS_BOOST_EN 6
+#define BATTMGR_CHG_CTRL_LIMIT_EN 0x48
+#define CHARGE_CTRL_START_THR_MIN 50
+#define CHARGE_CTRL_START_THR_MAX 95
+#define CHARGE_CTRL_END_THR_MIN 55
+#define CHARGE_CTRL_END_THR_MAX 100
+#define CHARGE_CTRL_DELTA_SOC 5
+
struct qcom_battmgr_enable_request {
struct pmic_glink_hdr hdr;
__le32 battery_id;
@@ -123,6 +138,13 @@ struct qcom_battmgr_discharge_time_request {
__le32 reserved;
};
+struct qcom_battmgr_charge_ctrl_request {
+ struct pmic_glink_hdr hdr;
+ __le32 enable;
+ __le32 target_soc;
+ __le32 delta_soc;
+};
+
struct qcom_battmgr_message {
struct pmic_glink_hdr hdr;
union {
@@ -235,6 +257,8 @@ struct qcom_battmgr_info {
unsigned int capacity_warning;
unsigned int cycle_count;
unsigned int charge_count;
+ unsigned int charge_ctrl_start;
+ unsigned int charge_ctrl_end;
char model_number[BATTMGR_STRING_LEN];
char serial_number[BATTMGR_STRING_LEN];
char oem_info[BATTMGR_STRING_LEN];
@@ -254,6 +278,8 @@ struct qcom_battmgr_status {
unsigned int voltage_now;
unsigned int voltage_ocv;
unsigned int temperature;
+ unsigned int resistance;
+ unsigned int soh_percent;
unsigned int discharge_time;
unsigned int charge_time;
@@ -418,7 +444,11 @@ static const u8 sm8350_bat_prop_map[] = {
[POWER_SUPPLY_PROP_MODEL_NAME] = BATT_MODEL_NAME,
[POWER_SUPPLY_PROP_TIME_TO_FULL_AVG] = BATT_TTF_AVG,
[POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG] = BATT_TTE_AVG,
+ [POWER_SUPPLY_PROP_INTERNAL_RESISTANCE] = BATT_RESISTANCE,
+ [POWER_SUPPLY_PROP_STATE_OF_HEALTH] = BATT_SOH,
[POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW,
+ [POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD] = BATT_CHG_CTRL_START_THR,
+ [POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD] = BATT_CHG_CTRL_END_THR,
};
static int qcom_battmgr_bat_sm8350_update(struct qcom_battmgr *battmgr,
@@ -489,7 +519,8 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_bat_sm8350_update(battmgr, psp);
@@ -584,12 +615,24 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_TEMP:
val->intval = battmgr->status.temperature;
break;
+ case POWER_SUPPLY_PROP_INTERNAL_RESISTANCE:
+ val->intval = battmgr->status.resistance;
+ break;
+ case POWER_SUPPLY_PROP_STATE_OF_HEALTH:
+ val->intval = battmgr->status.soh_percent;
+ break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
val->intval = battmgr->status.discharge_time;
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
val->intval = battmgr->status.charge_time;
break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ val->intval = battmgr->info.charge_ctrl_start;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ val->intval = battmgr->info.charge_ctrl_end;
+ break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
val->intval = battmgr->info.year;
break;
@@ -615,6 +658,149 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy,
return 0;
}
+static int qcom_battmgr_set_charge_control(struct qcom_battmgr *battmgr,
+ u32 target_soc, u32 delta_soc)
+{
+ struct qcom_battmgr_charge_ctrl_request request = {
+ .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR),
+ .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
+ .hdr.opcode = cpu_to_le32(BATTMGR_CHG_CTRL_LIMIT_EN),
+ .enable = cpu_to_le32(1),
+ .target_soc = cpu_to_le32(target_soc),
+ .delta_soc = cpu_to_le32(delta_soc),
+ };
+
+ return qcom_battmgr_request(battmgr, &request, sizeof(request));
+}
+
+static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, int start_soc)
+{
+ u32 target_soc, delta_soc;
+ int ret;
+
+ if (start_soc < CHARGE_CTRL_START_THR_MIN ||
+ start_soc > CHARGE_CTRL_START_THR_MAX) {
+ dev_err(battmgr->dev, "charge control start threshold exceed range: [%u - %u]\n",
+ CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
+ return -EINVAL;
+ }
+
+ /*
+ * If the new start threshold is larger than the old end threshold,
+ * move the end threshold one step (DELTA_SOC) after the new start
+ * threshold.
+ */
+ if (start_soc > battmgr->info.charge_ctrl_end) {
+ target_soc = start_soc + CHARGE_CTRL_DELTA_SOC;
+ target_soc = min_t(u32, target_soc, CHARGE_CTRL_END_THR_MAX);
+ delta_soc = target_soc - start_soc;
+ delta_soc = min_t(u32, delta_soc, CHARGE_CTRL_DELTA_SOC);
+ } else {
+ target_soc = battmgr->info.charge_ctrl_end;
+ delta_soc = battmgr->info.charge_ctrl_end - start_soc;
+ }
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_set_charge_control(battmgr, target_soc, delta_soc);
+ mutex_unlock(&battmgr->lock);
+ if (!ret) {
+ battmgr->info.charge_ctrl_start = start_soc;
+ battmgr->info.charge_ctrl_end = target_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, int end_soc)
+{
+ u32 delta_soc = CHARGE_CTRL_DELTA_SOC;
+ int ret;
+
+ if (end_soc < CHARGE_CTRL_END_THR_MIN ||
+ end_soc > CHARGE_CTRL_END_THR_MAX) {
+ dev_err(battmgr->dev, "charge control end threshold exceed range: [%u - %u]\n",
+ CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
+ return -EINVAL;
+ }
+
+ if (battmgr->info.charge_ctrl_start && end_soc > battmgr->info.charge_ctrl_start)
+ delta_soc = end_soc - battmgr->info.charge_ctrl_start;
+
+ mutex_lock(&battmgr->lock);
+ ret = qcom_battmgr_set_charge_control(battmgr, end_soc, delta_soc);
+ mutex_unlock(&battmgr->lock);
+ if (!ret) {
+ battmgr->info.charge_ctrl_start = end_soc - delta_soc;
+ battmgr->info.charge_ctrl_end = end_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_charge_control_thresholds_init(struct qcom_battmgr *battmgr)
+{
+ int ret;
+ u8 en, end_soc, start_soc, delta_soc;
+
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_en", &en);
+ if (!ret && en != 0) {
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_end", &end_soc);
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_u8(battmgr->dev->parent, "charge_limit_delta", &delta_soc);
+ if (ret < 0)
+ return ret;
+
+ if (delta_soc >= end_soc)
+ return -EINVAL;
+
+ start_soc = end_soc - delta_soc;
+ end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
+ start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
+
+ battmgr->info.charge_ctrl_start = start_soc;
+ battmgr->info.charge_ctrl_end = end_soc;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_bat_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return 1;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int qcom_battmgr_bat_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy);
+
+ if (!battmgr->service_up)
+ return -EAGAIN;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ return qcom_battmgr_set_charge_start_threshold(battmgr, pval->intval);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return qcom_battmgr_set_charge_end_threshold(battmgr, pval->intval);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const enum power_supply_property sc8280xp_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
@@ -649,6 +835,43 @@ static const struct power_supply_desc sc8280xp_bat_psy_desc = {
.get_property = qcom_battmgr_bat_get_property,
};
+static const enum power_supply_property x1e80100_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_EMPTY,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
+ POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
+ POWER_SUPPLY_PROP_MANUFACTURE_DAY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_desc x1e80100_bat_psy_desc = {
+ .name = "qcom-battmgr-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = x1e80100_bat_props,
+ .num_properties = ARRAY_SIZE(x1e80100_bat_props),
+ .get_property = qcom_battmgr_bat_get_property,
+ .set_property = qcom_battmgr_bat_set_property,
+ .property_is_writeable = qcom_battmgr_bat_is_writeable,
+};
+
static const enum power_supply_property sm8350_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
@@ -668,6 +891,8 @@ static const enum power_supply_property sm8350_bat_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
+ POWER_SUPPLY_PROP_STATE_OF_HEALTH,
POWER_SUPPLY_PROP_POWER_NOW,
};
@@ -679,6 +904,42 @@ static const struct power_supply_desc sm8350_bat_psy_desc = {
.get_property = qcom_battmgr_bat_get_property,
};
+static const enum power_supply_property sm8550_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
+ POWER_SUPPLY_PROP_STATE_OF_HEALTH,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_desc sm8550_bat_psy_desc = {
+ .name = "qcom-battmgr-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = sm8550_bat_props,
+ .num_properties = ARRAY_SIZE(sm8550_bat_props),
+ .get_property = qcom_battmgr_bat_get_property,
+ .set_property = qcom_battmgr_bat_set_property,
+ .property_is_writeable = qcom_battmgr_bat_is_writeable,
+};
+
static int qcom_battmgr_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -754,7 +1015,8 @@ static int qcom_battmgr_usb_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_usb_sm8350_update(battmgr, psp);
@@ -876,7 +1138,8 @@ static int qcom_battmgr_wls_get_property(struct power_supply *psy,
if (!battmgr->service_up)
return -EAGAIN;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp);
else
ret = qcom_battmgr_wls_sm8350_update(battmgr, psp);
@@ -947,12 +1210,14 @@ static void qcom_battmgr_notification(struct qcom_battmgr *battmgr,
}
notification = le32_to_cpu(msg->notification);
+ notification &= 0xff;
switch (notification) {
case NOTIF_BAT_INFO:
battmgr->info.valid = false;
fallthrough;
case NOTIF_BAT_STATUS:
case NOTIF_BAT_PROPERTY:
+ case NOTIF_BAT_CHARGING_STATE:
power_supply_changed(battmgr->bat_psy);
break;
case NOTIF_USB_PROPERTY:
@@ -982,7 +1247,8 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src)
static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry)
{
- if (!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN))
+ if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) ||
+ (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN)))
return POWER_SUPPLY_TECHNOLOGY_LION;
if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN))
return POWER_SUPPLY_TECHNOLOGY_LIPO;
@@ -1095,6 +1361,9 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
case BATTMGR_BAT_CHARGE_TIME:
battmgr->status.charge_time = le32_to_cpu(resp->time);
break;
+ case BATTMGR_CHG_CTRL_LIMIT_EN:
+ battmgr->error = 0;
+ break;
default:
dev_warn(battmgr->dev, "unknown message %#x\n", opcode);
break;
@@ -1159,6 +1428,9 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
case BATT_CAPACITY:
battmgr->status.percent = le32_to_cpu(resp->intval.value) / 100;
break;
+ case BATT_SOH:
+ battmgr->status.soh_percent = le32_to_cpu(resp->intval.value);
+ break;
case BATT_VOLT_OCV:
battmgr->status.voltage_ocv = le32_to_cpu(resp->intval.value);
break;
@@ -1199,9 +1471,18 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
case BATT_TTE_AVG:
battmgr->status.discharge_time = le32_to_cpu(resp->intval.value);
break;
+ case BATT_RESISTANCE:
+ battmgr->status.resistance = le32_to_cpu(resp->intval.value);
+ break;
case BATT_POWER_NOW:
battmgr->status.power_now = le32_to_cpu(resp->intval.value);
break;
+ case BATT_CHG_CTRL_START_THR:
+ battmgr->info.charge_ctrl_start = le32_to_cpu(resp->intval.value);
+ break;
+ case BATT_CHG_CTRL_END_THR:
+ battmgr->info.charge_ctrl_end = le32_to_cpu(resp->intval.value);
+ break;
default:
dev_warn(battmgr->dev, "unknown property %#x\n", property);
break;
@@ -1284,6 +1565,7 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
}
break;
case BATTMGR_REQUEST_NOTIFICATION:
+ case BATTMGR_CHG_CTRL_LIMIT_EN:
battmgr->error = 0;
break;
default:
@@ -1303,7 +1585,8 @@ static void qcom_battmgr_callback(const void *data, size_t len, void *priv)
if (opcode == BATTMGR_NOTIFICATION)
qcom_battmgr_notification(battmgr, data, len);
- else if (battmgr->variant == QCOM_BATTMGR_SC8280XP)
+ else if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100)
qcom_battmgr_sc8280xp_callback(battmgr, data, len);
else
qcom_battmgr_sm8350_callback(battmgr, data, len);
@@ -1339,7 +1622,8 @@ static void qcom_battmgr_pdr_notify(void *priv, int state)
static const struct of_device_id qcom_battmgr_of_variants[] = {
{ .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
{ .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
- { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
+ { .compatible = "qcom,sm8550-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 },
+ { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 },
/* Unmatched devices falls back to QCOM_BATTMGR_SM8350 */
{}
};
@@ -1349,11 +1633,13 @@ static char *qcom_battmgr_battery[] = { "battery" };
static int qcom_battmgr_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
+ const struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg_supply = {};
struct power_supply_config psy_cfg = {};
const struct of_device_id *match;
struct qcom_battmgr *battmgr;
struct device *dev = &adev->dev;
+ int ret;
battmgr = devm_kzalloc(dev, sizeof(*battmgr), GFP_KERNEL);
if (!battmgr)
@@ -1379,8 +1665,19 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
else
battmgr->variant = QCOM_BATTMGR_SM8350;
- if (battmgr->variant == QCOM_BATTMGR_SC8280XP) {
- battmgr->bat_psy = devm_power_supply_register(dev, &sc8280xp_bat_psy_desc, &psy_cfg);
+ ret = qcom_battmgr_charge_control_thresholds_init(battmgr);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to init battery charge control thresholds\n");
+
+ if (battmgr->variant == QCOM_BATTMGR_SC8280XP ||
+ battmgr->variant == QCOM_BATTMGR_X1E80100) {
+ if (battmgr->variant == QCOM_BATTMGR_X1E80100)
+ psy_desc = &x1e80100_bat_psy_desc;
+ else
+ psy_desc = &sc8280xp_bat_psy_desc;
+
+ battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(battmgr->bat_psy))
return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy),
"failed to register battery power supply\n");
@@ -1400,7 +1697,12 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev,
return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy),
"failed to register wireless charing power supply\n");
} else {
- battmgr->bat_psy = devm_power_supply_register(dev, &sm8350_bat_psy_desc, &psy_cfg);
+ if (battmgr->variant == QCOM_BATTMGR_SM8550)
+ psy_desc = &sm8550_bat_psy_desc;
+ else
+ psy_desc = &sm8350_bat_psy_desc;
+
+ battmgr->bat_psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(battmgr->bat_psy))
return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy),
"failed to register battery power supply\n");
diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c
index 1251022eb052..9436c6bbf51f 100644
--- a/drivers/power/supply/rk817_charger.c
+++ b/drivers/power/supply/rk817_charger.c
@@ -1046,7 +1046,7 @@ static void rk817_charging_monitor(struct work_struct *work)
rk817_read_props(charger);
/* Run every 8 seconds like the BSP driver did. */
- queue_delayed_work(system_wq, &charger->work, msecs_to_jiffies(8000));
+ queue_delayed_work(system_percpu_wq, &charger->work, msecs_to_jiffies(8000));
}
static void rk817_cleanup_node(void *data)
@@ -1206,7 +1206,7 @@ static int rk817_charger_probe(struct platform_device *pdev)
return ret;
/* Force the first update immediately. */
- mod_delayed_work(system_wq, &charger->work, 0);
+ mod_delayed_work(system_percpu_wq, &charger->work, 0);
return 0;
}
@@ -1226,7 +1226,7 @@ static int __maybe_unused rk817_resume(struct device *dev)
struct rk817_charger *charger = dev_get_drvdata(dev);
/* force an immediate update */
- mod_delayed_work(system_wq, &charger->work, 0);
+ mod_delayed_work(system_percpu_wq, &charger->work, 0);
return 0;
}
diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c
index e9aba9ad393c..fe773dd8b404 100644
--- a/drivers/power/supply/rt9467-charger.c
+++ b/drivers/power/supply/rt9467-charger.c
@@ -633,7 +633,9 @@ out:
static const enum power_supply_property rt9467_chg_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
@@ -656,6 +658,8 @@ static int rt9467_psy_get_property(struct power_supply *psy,
return rt9467_psy_get_status(data, &val->intval);
case POWER_SUPPLY_PROP_ONLINE:
return regmap_field_read(data->rm_field[F_PWR_RDY], &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return rt9467_get_adc(data, RT9467_ADC_VBUS_DIV5, &val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX:
mutex_lock(&data->attach_lock);
if (data->psy_usb_type == POWER_SUPPLY_USB_TYPE_UNKNOWN ||
@@ -665,6 +669,8 @@ static int rt9467_psy_get_property(struct power_supply *psy,
val->intval = 1500000;
mutex_unlock(&data->attach_lock);
return 0;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return rt9467_get_adc(data, RT9467_ADC_IBUS, &val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
mutex_lock(&data->ichg_ieoc_lock);
val->intval = data->ichg_ua;
@@ -1141,27 +1147,6 @@ static int rt9467_reset_chip(struct rt9467_chg_data *data)
return regmap_field_write(data->rm_field[F_RST], 1);
}
-static void rt9467_chg_destroy_adc_lock(void *data)
-{
- struct mutex *adc_lock = data;
-
- mutex_destroy(adc_lock);
-}
-
-static void rt9467_chg_destroy_attach_lock(void *data)
-{
- struct mutex *attach_lock = data;
-
- mutex_destroy(attach_lock);
-}
-
-static void rt9467_chg_destroy_ichg_ieoc_lock(void *data)
-{
- struct mutex *ichg_ieoc_lock = data;
-
- mutex_destroy(ichg_ieoc_lock);
-}
-
static void rt9467_chg_complete_aicl_done(void *data)
{
struct completion *aicl_done = data;
@@ -1214,29 +1199,23 @@ static int rt9467_charger_probe(struct i2c_client *i2c)
if (ret)
return dev_err_probe(dev, ret, "Failed to add irq chip\n");
- mutex_init(&data->adc_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_adc_lock,
- &data->adc_lock);
+ ret = devm_mutex_init(dev, &data->adc_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init ADC lock\n");
+ return ret;
- mutex_init(&data->attach_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_attach_lock,
- &data->attach_lock);
+ ret = devm_mutex_init(dev, &data->attach_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init attach lock\n");
+ return ret;
- mutex_init(&data->ichg_ieoc_lock);
- ret = devm_add_action_or_reset(dev, rt9467_chg_destroy_ichg_ieoc_lock,
- &data->ichg_ieoc_lock);
+ ret = devm_mutex_init(dev, &data->ichg_ieoc_lock);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init ICHG/IEOC lock\n");
+ return ret;
init_completion(&data->aicl_done);
ret = devm_add_action_or_reset(dev, rt9467_chg_complete_aicl_done,
&data->aicl_done);
if (ret)
- return dev_err_probe(dev, ret, "Failed to init AICL done completion\n");
+ return ret;
ret = rt9467_do_charger_init(data);
if (ret)
diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c
index 7cdcd415e868..b0220ec2d926 100644
--- a/drivers/power/supply/rx51_battery.c
+++ b/drivers/power/supply/rx51_battery.c
@@ -116,7 +116,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
int mid = (max + min) / 2;
if (rx51_temp_table2[mid] <= raw)
min = mid;
- else if (rx51_temp_table2[mid] > raw)
+ else
max = mid;
if (rx51_temp_table2[mid] == raw)
break;
diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c
index 27764123b929..7d5e67620580 100644
--- a/drivers/power/supply/sbs-charger.c
+++ b/drivers/power/supply/sbs-charger.c
@@ -154,8 +154,7 @@ static const struct regmap_config sbs_regmap = {
.val_format_endian = REGMAP_ENDIAN_LITTLE, /* since based on SMBus */
};
-static const struct power_supply_desc sbs_desc = {
- .name = "sbs-charger",
+static const struct power_supply_desc sbs_default_desc = {
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = sbs_properties,
.num_properties = ARRAY_SIZE(sbs_properties),
@@ -165,9 +164,20 @@ static const struct power_supply_desc sbs_desc = {
static int sbs_probe(struct i2c_client *client)
{
struct power_supply_config psy_cfg = {};
+ struct power_supply_desc *sbs_desc;
struct sbs_info *chip;
int ret, val;
+ sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc,
+ sizeof(*sbs_desc), GFP_KERNEL);
+ if (!sbs_desc)
+ return -ENOMEM;
+
+ sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s",
+ dev_name(&client->dev));
+ if (!sbs_desc->name)
+ return -ENOMEM;
+
chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL);
if (!chip)
return -ENOMEM;
@@ -191,7 +201,7 @@ static int sbs_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, ret, "Failed to get device status\n");
chip->last_state = val;
- chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc, &psy_cfg);
+ chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg);
if (IS_ERR(chip->power_supply))
return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply),
"Failed to register power supply\n");
diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c
index 869729dfcd66..6fe526222f7f 100644
--- a/drivers/power/supply/sbs-manager.c
+++ b/drivers/power/supply/sbs-manager.c
@@ -348,7 +348,7 @@ static int sbsm_probe(struct i2c_client *client)
data->muxc = i2c_mux_alloc(adapter, dev, SBSM_MAX_BATS, 0,
I2C_MUX_LOCKED, &sbsm_select, NULL);
if (!data->muxc)
- return dev_err_probe(dev, -ENOMEM, "failed to alloc i2c mux\n");
+ return -ENOMEM;
data->muxc->priv = data;
ret = devm_add_action_or_reset(dev, sbsm_del_mux_adapter, data);
diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c
index d32a7633f9e7..fe94435340de 100644
--- a/drivers/power/supply/ucs1002_power.c
+++ b/drivers/power/supply/ucs1002_power.c
@@ -493,7 +493,7 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data)
{
struct ucs1002_info *info = data;
- mod_delayed_work(system_wq, &info->health_poll, 0);
+ mod_delayed_work(system_percpu_wq, &info->health_poll, 0);
return IRQ_HANDLED;
}
diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c
index e8a1de7cade0..210e0f9aa5e0 100644
--- a/drivers/power/supply/ug3105_battery.c
+++ b/drivers/power/supply/ug3105_battery.c
@@ -10,7 +10,22 @@
* is off or suspended, the coulomb counter is not used atm.
*
* Possible improvements:
- * 1. Activate commented out total_coulomb_count code
+ * 1. Add coulumb counter reading, e.g. something like this:
+ * Read + reset coulomb counter every 10 polls (every 300 seconds)
+ *
+ * if ((chip->poll_count % 10) == 0) {
+ * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
+ * if (val < 0)
+ * goto out;
+ *
+ * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
+ * UG3105_CTRL1_RESET_COULOMB_CNT);
+ *
+ * chip->total_coulomb_count += (s16)val;
+ * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
+ * (s16)val, chip->total_coulomb_count);
+ * }
+ *
* 2. Reset total_coulomb_count val to 0 when the battery is as good as empty
* and remember that we did this (and clear the flag for this on susp/resume)
* 3. When the battery is full check if the flag that we set total_coulomb_count
@@ -31,24 +46,16 @@
* has shown that an estimated 7404mWh increase of the battery's energy results
* in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R.
*
- * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021 - 2025 Hans de Goede <hansg@kernel.org>
*/
-#include <linux/devm-helpers.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
-#include <linux/workqueue.h>
-
-#define UG3105_MOV_AVG_WINDOW 8
-#define UG3105_INIT_POLL_TIME (5 * HZ)
-#define UG3105_POLL_TIME (30 * HZ)
-#define UG3105_SETTLE_TIME (1 * HZ)
-#define UG3105_INIT_POLL_COUNT 30
+#include "adc-battery-helper.h"
#define UG3105_REG_MODE 0x00
#define UG3105_REG_CTRL1 0x01
@@ -61,34 +68,13 @@
#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03
-#define UG3105_CURR_HYST_UA 65000
-
-#define UG3105_LOW_BAT_UV 3700000
-#define UG3105_FULL_BAT_HYST_UV 38000
-
-#define AMBIENT_TEMP_CELCIUS 25
-
struct ug3105_chip {
+ /* Must be the first member see adc-battery-helper documentation */
+ struct adc_battery_helper helper;
struct i2c_client *client;
struct power_supply *psy;
- struct delayed_work work;
- struct mutex lock;
- int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */
- int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */
- int poll_count;
- int ocv_avg_index;
- int ocv_avg; /* micro-volt */
- int intern_res_poll_count;
- int intern_res_avg_index;
- int intern_res_avg; /* milli-ohm */
- int volt; /* micro-volt */
- int curr; /* micro-ampere */
- int total_coulomb_count;
int uv_per_unit;
int ua_per_unit;
- int status;
- int capacity;
- bool supplied;
};
static int ug3105_read_word(struct i2c_client *client, u8 reg)
@@ -102,230 +88,43 @@ static int ug3105_read_word(struct i2c_client *client, u8 reg)
return val;
}
-static int ug3105_get_status(struct ug3105_chip *chip)
-{
- int full = chip->psy->battery_info->constant_charge_voltage_max_uv -
- UG3105_FULL_BAT_HYST_UV;
-
- if (chip->curr > UG3105_CURR_HYST_UA)
- return POWER_SUPPLY_STATUS_CHARGING;
-
- if (chip->curr < -UG3105_CURR_HYST_UA)
- return POWER_SUPPLY_STATUS_DISCHARGING;
-
- if (chip->supplied && chip->ocv_avg > full)
- return POWER_SUPPLY_STATUS_FULL;
-
- return POWER_SUPPLY_STATUS_NOT_CHARGING;
-}
-
-static void ug3105_work(struct work_struct *work)
-{
- struct ug3105_chip *chip = container_of(work, struct ug3105_chip,
- work.work);
- int i, val, curr_diff, volt_diff, res, win_size;
- bool prev_supplied = chip->supplied;
- int prev_status = chip->status;
- int prev_volt = chip->volt;
- int prev_curr = chip->curr;
- struct power_supply *psy;
-
- mutex_lock(&chip->lock);
-
- psy = chip->psy;
- if (!psy)
- goto out;
-
- val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
- if (val < 0)
- goto out;
- chip->volt = val * chip->uv_per_unit;
-
- val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
- if (val < 0)
- goto out;
- chip->curr = (s16)val * chip->ua_per_unit;
-
- chip->ocv[chip->ocv_avg_index] =
- chip->volt - chip->curr * chip->intern_res_avg / 1000;
- chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
- chip->poll_count++;
-
- /*
- * See possible improvements comment above.
- *
- * Read + reset coulomb counter every 10 polls (every 300 seconds)
- * if ((chip->poll_count % 10) == 0) {
- * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
- * if (val < 0)
- * goto out;
- *
- * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
- * UG3105_CTRL1_RESET_COULOMB_CNT);
- *
- * chip->total_coulomb_count += (s16)val;
- * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
- * (s16)val, chip->total_coulomb_count);
- * }
- */
-
- chip->ocv_avg = 0;
- win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW);
- for (i = 0; i < win_size; i++)
- chip->ocv_avg += chip->ocv[i];
- chip->ocv_avg /= win_size;
-
- chip->supplied = power_supply_am_i_supplied(psy);
- chip->status = ug3105_get_status(chip);
- if (chip->status == POWER_SUPPLY_STATUS_FULL)
- chip->capacity = 100;
- else
- chip->capacity = power_supply_batinfo_ocv2cap(chip->psy->battery_info,
- chip->ocv_avg,
- AMBIENT_TEMP_CELCIUS);
-
- /*
- * Skip internal resistance calc on charger [un]plug and
- * when the battery is almost empty (voltage low).
- */
- if (chip->supplied != prev_supplied ||
- chip->volt < UG3105_LOW_BAT_UV ||
- chip->poll_count < 2)
- goto out;
-
- /*
- * Assuming that the OCV voltage does not change significantly
- * between 2 polls, then we can calculate the internal resistance
- * on a significant current change by attributing all voltage
- * change between the 2 readings to the internal resistance.
- */
- curr_diff = abs(chip->curr - prev_curr);
- if (curr_diff < UG3105_CURR_HYST_UA)
- goto out;
-
- volt_diff = abs(chip->volt - prev_volt);
- res = volt_diff * 1000 / curr_diff;
-
- if ((res < (chip->intern_res_avg * 2 / 3)) ||
- (res > (chip->intern_res_avg * 4 / 3))) {
- dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res);
- goto out;
- }
-
- dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res);
-
- chip->intern_res[chip->intern_res_avg_index] = res;
- chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
- chip->intern_res_poll_count++;
-
- chip->intern_res_avg = 0;
- win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW);
- for (i = 0; i < win_size; i++)
- chip->intern_res_avg += chip->intern_res[i];
- chip->intern_res_avg /= win_size;
-
-out:
- mutex_unlock(&chip->lock);
-
- queue_delayed_work(system_wq, &chip->work,
- (chip->poll_count <= UG3105_INIT_POLL_COUNT) ?
- UG3105_INIT_POLL_TIME : UG3105_POLL_TIME);
-
- if (chip->status != prev_status && psy)
- power_supply_changed(psy);
-}
-
-static enum power_supply_property ug3105_battery_props[] = {
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_PRESENT,
- POWER_SUPPLY_PROP_SCOPE,
- POWER_SUPPLY_PROP_VOLTAGE_NOW,
- POWER_SUPPLY_PROP_VOLTAGE_OCV,
- POWER_SUPPLY_PROP_CURRENT_NOW,
- POWER_SUPPLY_PROP_CAPACITY,
-};
-
-static int ug3105_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
{
struct ug3105_chip *chip = power_supply_get_drvdata(psy);
- int ret = 0;
-
- mutex_lock(&chip->lock);
+ int ret;
- if (!chip->psy) {
- ret = -EAGAIN;
- goto out;
- }
+ ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
+ if (ret < 0)
+ return ret;
- switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- val->intval = chip->status;
- break;
- case POWER_SUPPLY_PROP_PRESENT:
- val->intval = 1;
- break;
- case POWER_SUPPLY_PROP_SCOPE:
- val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
- break;
- case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
- if (ret < 0)
- break;
- val->intval = ret * chip->uv_per_unit;
- ret = 0;
- break;
- case POWER_SUPPLY_PROP_VOLTAGE_OCV:
- val->intval = chip->ocv_avg;
- break;
- case POWER_SUPPLY_PROP_CURRENT_NOW:
- ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
- if (ret < 0)
- break;
- val->intval = (s16)ret * chip->ua_per_unit;
- ret = 0;
- break;
- case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = chip->capacity;
- break;
- default:
- ret = -EINVAL;
- }
+ *volt = ret * chip->uv_per_unit;
-out:
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static void ug3105_external_power_changed(struct power_supply *psy)
-{
- struct ug3105_chip *chip = power_supply_get_drvdata(psy);
+ ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
+ if (ret < 0)
+ return ret;
- dev_dbg(&chip->client->dev, "external power changed\n");
- mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
+ *curr = (s16)ret * chip->ua_per_unit;
+ return 0;
}
static const struct power_supply_desc ug3105_psy_desc = {
.name = "ug3105_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
- .get_property = ug3105_get_property,
- .external_power_changed = ug3105_external_power_changed,
- .properties = ug3105_battery_props,
- .num_properties = ARRAY_SIZE(ug3105_battery_props),
+ .get_property = adc_battery_helper_get_property,
+ .external_power_changed = adc_battery_helper_external_power_changed,
+ .properties = adc_battery_helper_properties,
+ .num_properties = ADC_HELPER_NUM_PROPERTIES,
};
-static void ug3105_init(struct ug3105_chip *chip)
+static void ug3105_start(struct i2c_client *client)
+{
+ i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN);
+ i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT);
+}
+
+static void ug3105_stop(struct i2c_client *client)
{
- chip->poll_count = 0;
- chip->ocv_avg_index = 0;
- chip->total_coulomb_count = 0;
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
- UG3105_MODE_RUN);
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
- UG3105_CTRL1_RESET_COULOMB_CNT);
- queue_delayed_work(system_wq, &chip->work, 0);
- flush_delayed_work(&chip->work);
+ i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY);
}
static int ug3105_probe(struct i2c_client *client)
@@ -333,7 +132,6 @@ static int ug3105_probe(struct i2c_client *client)
struct power_supply_config psy_cfg = {};
struct device *dev = &client->dev;
u32 curr_sense_res_uohm = 10000;
- struct power_supply *psy;
struct ug3105_chip *chip;
int ret;
@@ -342,23 +140,8 @@ static int ug3105_probe(struct i2c_client *client)
return -ENOMEM;
chip->client = client;
- mutex_init(&chip->lock);
- ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work);
- if (ret)
- return ret;
- psy_cfg.drv_data = chip;
- psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
- if (IS_ERR(psy))
- return PTR_ERR(psy);
-
- if (!psy->battery_info ||
- psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
- psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
- !psy->battery_info->ocv_table[0]) {
- dev_err(dev, "error required properties are missing\n");
- return -ENODEV;
- }
+ ug3105_start(client);
device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm);
@@ -366,35 +149,36 @@ static int ug3105_probe(struct i2c_client *client)
* DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10
* coming from somewhere for some reason (verified with a volt-meter).
*/
- chip->uv_per_unit = 45000000/65536;
+ chip->uv_per_unit = 45000000 / 65536;
/* Datasheet says 8.1 uV per unit for the current ADC */
chip->ua_per_unit = 8100000 / curr_sense_res_uohm;
- /* Use provided internal resistance as start point (in milli-ohm) */
- chip->intern_res_avg = psy->battery_info->factory_internal_resistance_uohm / 1000;
- /* Also add it to the internal resistance moving average window */
- chip->intern_res[0] = chip->intern_res_avg;
- chip->intern_res_avg_index = 1;
- chip->intern_res_poll_count = 1;
-
- mutex_lock(&chip->lock);
- chip->psy = psy;
- mutex_unlock(&chip->lock);
+ psy_cfg.drv_data = chip;
+ chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
+ if (IS_ERR(chip->psy)) {
+ ret = PTR_ERR(chip->psy);
+ goto stop;
+ }
- ug3105_init(chip);
+ ret = adc_battery_helper_init(&chip->helper, chip->psy,
+ ug3105_get_voltage_and_current_now, NULL);
+ if (ret)
+ goto stop;
i2c_set_clientdata(client, chip);
return 0;
+
+stop:
+ ug3105_stop(client);
+ return ret;
}
static int __maybe_unused ug3105_suspend(struct device *dev)
{
struct ug3105_chip *chip = dev_get_drvdata(dev);
- cancel_delayed_work_sync(&chip->work);
- i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
- UG3105_MODE_STANDBY);
-
+ adc_battery_helper_suspend(dev);
+ ug3105_stop(chip->client);
return 0;
}
@@ -402,8 +186,8 @@ static int __maybe_unused ug3105_resume(struct device *dev)
{
struct ug3105_chip *chip = dev_get_drvdata(dev);
- ug3105_init(chip);
-
+ ug3105_start(chip->client);
+ adc_battery_helper_resume(dev);
return 0;
}
@@ -422,10 +206,12 @@ static struct i2c_driver ug3105_i2c_driver = {
.pm = &ug3105_pm_ops,
},
.probe = ug3105_probe,
+ .remove = ug3105_stop,
+ .shutdown = ug3105_stop,
.id_table = ug3105_id,
};
module_i2c_driver(ug3105_i2c_driver);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org");
MODULE_DESCRIPTION("uPI uG3105 battery monitor driver");
MODULE_LICENSE("GPL");