power supply and reset changes for the 6.18 series

* power-supply core
   - introduce adc-battery-helper for capacity estimation based on
     simple ADC readings of battery voltage and current
   - add new property for battery internal resistance
   - add new property for battery state of health
  * power-supply drivers
   - ug3105_battery: convert to adc-battery-helper
   - intel_dc_ti_battery: New driver for Intel Dollar Cove TI batteries
   - rt9467-charger: add voltage and current ADC support
   - sbs-charger: support multiple instances
   - qcom_battmgr: add charge control support
   - qcom_battmgr: add support for state of health and internal resistance
   - max77705_charger: big driver cleanup
   - max77705_charger: add support for setting charge current
   - misc. minor fixes and cleanups
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmjcV2cACgkQ2O7X88g7
 +pol1RAAkGjNlitEnD+j5HB3i2ROjVg1oOd/iBiIwZ9wEpOemy/bNEpt9M69U4jJ
 IRKPJcnjOf37koWegyIoQ/te1qAdiXZm7oc/SIN63hn0U+nb2HB8TDVP7J9nzG3b
 qTTViSOciJfpmwn7P/jFehY3oL29eMELamMn5AnVx5jCH0Baiw4qQSRCG81s40hK
 6+WdLXhcQBeTzwj69FvTgdQ1McIiAgciGEN+3t5vB/3D4gstMV22DyrKuJOX9NNk
 4wdSb5t3O+JuQ819T02O4ovyp1JHC0CPm/Si2n+TRBWmXEWJu/so18SQvT1u9SYb
 /9p83bZJLRF7icWRGXHvM/eqV/K+G5MMHLKY+5tEG4gFLnKZl3WaTG1gAuoTNqx0
 unR95ipWbQjy7z90QGlxF1+Ui3ScdwHic2fZ3DW0Bl2aIXARCJ0jOU0ShZAPS7Ff
 NvVDoj/2/IQSzl0UJDb2uyW5S3Vl94f8z1w3xDmV3atA9ccioyTibTjiULPw42C4
 GVtRHJFjQW2FVQ+rIvnjc3BHn2i6Tqj0qVHQQUH8xL17TXg2cK6KkTIB3LuyFKai
 ol74I6VUsEKLZbKM0s+7Dyukwf+YlgpQpG7M+B1v7MQbPMHdiG3WUAF8JHud2/Dz
 6Tiq/IGzwbse0DzVSiC3KT3CX/DohkFKLYdRkTBmd38aVV/i6b4=
 =UcWZ
 -----END PGP SIGNATURE-----

Merge tag 'for-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply

Pull power supply and reset updates from Sebastian Reichel:
 "Power-supply core:
   - introduce adc-battery-helper for capacity estimation based on
     simple ADC readings of battery voltage and current
   - add new properties for battery internal resistance and state of
     health

  Power-supply drivers:
   - ug3105_battery: convert to adc-battery-helper
   - intel_dc_ti_battery: New driver for Intel Dollar Cove TI batteries
   - rt9467-charger: add voltage and current ADC support
   - sbs-charger: support multiple instances
   - qcom_battmgr:
       - add charge control support
       - add support for state of health and internal resistance
   - max77705_charger:
       - big driver cleanup
       - add support for setting charge current
   - misc minor fixes and cleanups"

* tag 'for-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (38 commits)
  power: supply: qcom_battmgr: handle charging state change notifications
  power: supply: max77705_charger: use REGMAP_IRQ_REG_LINE macro
  power: supply: max77705_charger: rework interrupts
  power: supply: max77705_charger: add writable properties
  power: supply: max77705_charger: return error when config fails
  power: supply: max77705_charger: use regfields for config registers
  power: supply: max77705_charger: refactoring: rename charger to chg
  mfd: max77705: max77705_charger: move active discharge setting to mfd parent
  power: supply: max77976_charger: fix constant current reporting
  power: supply: qcom_battmgr: Add charge control support
  dt-bindings: soc: qcom,pmic-glink: Add charge limit nvmem properties
  power: supply: qcom_battmgr: update compats for SM8550 and X1E80100
  power: supply: qcom_battmgr: Add state_of_health property
  power: supply: qcom_battmgr: Add resistance power supply property
  power: supply: core: Add state_of_health power supply property
  power: supply: core: Add resistance power supply property
  power: supply: rx51: remove redundant condition checks
  dt-bindings: power: supply: bq24190: document charge enable pin
  dt-bindings: power: supply: bq27xxx: document optional interrupt
  power: supply: intel_dc_ti_battery: Drop no longer relevant comment
  ...
pull/1354/merge
Linus Torvalds 2025-10-01 13:02:59 -07:00
commit 3ee22ad492
32 changed files with 1609 additions and 589 deletions

View File

@ -553,6 +553,43 @@ Description:
Integer > 0: representing full cycles
Integer = 0: cycle_count info is not available
What: /sys/class/power_supply/<supply_name>/internal_resistance
Date: August 2025
Contact: linux-arm-msm@vger.kernel.org
Description:
Represent the battery's internal resistance, often referred
to as Equivalent Series Resistance (ESR). It is a dynamic
parameter that reflects the opposition to current flow within
the cell. It is not a fixed value but varies significantly
based on several operational conditions, including battery
state of charge (SoC), temperature, and whether the battery
is in a charging or discharging state.
Access: Read
Valid values: Represented in microohms
What: /sys/class/power_supply/<supply_name>/state_of_health
Date: August 2025
Contact: linux-arm-msm@vger.kernel.org
Description:
The state_of_health parameter quantifies the overall condition
of a battery as a percentage, reflecting its ability to deliver
rated performance relative to its original specifications. It is
dynamically computed using a combination of learned capacity
and impedance-based degradation indicators, both of which evolve
over the battery's lifecycle.
Note that the exact algorithms are kept secret by most battery
vendors and the value from different battery vendors cannot be
compared with each other as there is no vendor-agnostic definition
of "performance". Also this usually cannot be used for any
calculations (i.e. this is not the factor between charge_full and
charge_full_design).
Access: Read
Valid values: 0 - 100 (percent)
**USB Properties**
What: /sys/class/power_supply/<supply_name>/input_current_limit

View File

@ -30,6 +30,12 @@ properties:
interrupts:
maxItems: 1
ce-gpios:
description:
Active low Charge Enable pin. Battery charging is enabled when
REG01[5:4] = 01 and CE pin is Low. CE pin must be pulled high or low.
maxItems: 1
usb-otg-vbus:
$ref: /schemas/regulator/regulator.yaml#
description: |

View File

@ -16,9 +16,6 @@ description: |
Support various Texas Instruments fuel gauge devices that share similar
register maps and power supply properties
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
enum:
@ -58,6 +55,10 @@ properties:
maxItems: 1
description: integer, I2C address of the fuel gauge.
interrupts:
maxItems: 1
description: the SOC_INT or GPOUT pin
monitored-battery:
description: |
The fuel gauge uses the following battery properties:
@ -68,6 +69,36 @@ properties:
power-supplies: true
allOf:
- $ref: power-supply.yaml#
- if:
properties:
compatible:
contains:
enum:
- ti,bq27200
- ti,bq27210
- ti,bq27500 # deprecated, use revision specific property below
- ti,bq27510 # deprecated, use revision specific property below
- ti,bq27520 # deprecated, use revision specific property below
- ti,bq27500-1
- ti,bq27510g1
- ti,bq27510g2
- ti,bq27521
- ti,bq27541
- ti,bq27542
- ti,bq27546
- ti,bq27742
- ti,bq27545
- ti,bq27411
- ti,bq27z561
- ti,bq28z610
- ti,bq34z100
- ti,bq78z100
then:
properties:
interrupts: false
required:
- compatible
- reg

View File

@ -56,6 +56,20 @@ properties:
The array should contain a gpio entry for each PMIC Glink connector, in reg order.
It is defined that GPIO active level means "CC2" or Reversed/Flipped orientation.
nvmem-cells:
minItems: 3
maxItems: 3
description:
The nvmem cells contain the charge control settings, including the charge control
enable status, the battery state of charge (SoC) threshold for stopping charging,
and the battery SoC delta required to restart charging.
nvmem-cell-names:
items:
- const: charge_limit_en
- const: charge_limit_end
- const: charge_limit_delta
patternProperties:
'^connector@\d$':
$ref: /schemas/connector/usb-connector.yaml#

View File

@ -108,6 +108,9 @@ static int max77705_i2c_probe(struct i2c_client *i2c)
if (pmic_rev != MAX77705_PASS3)
return dev_err_probe(dev, -ENODEV, "Rev.0x%x is not tested\n", pmic_rev);
/* Active Discharge Enable */
regmap_update_bits(max77705->regmap, MAX77705_PMIC_REG_MAINCTRL1, 1, 1);
ret = devm_regmap_add_irq_chip(dev, max77705->regmap,
i2c->irq,
IRQF_ONESHOT, 0,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
err = regmap_field_write(chg->rfield[MAX77705_FCHGTIME], MAX77705_FCHGTIME_DISABLE);
if (err)
goto err;
/* 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_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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
int ret;
mutex_lock(&chip->lock);
ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
if (ret < 0)
return ret;
if (!chip->psy) {
ret = -EAGAIN;
goto out;
}
*volt = ret * chip->uv_per_unit;
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;
}
ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
if (ret < 0)
return ret;
out:
mutex_unlock(&chip->lock);
return ret;
}
static void ug3105_external_power_changed(struct power_supply *psy)
{
struct ug3105_chip *chip = power_supply_get_drvdata(psy);
dev_dbg(&chip->client->dev, "external power changed\n");
mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
*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)
{
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_RUN);
i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT);
}
static void ug3105_stop(struct i2c_client *client)
{
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;
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;
}
mutex_lock(&chip->lock);
chip->psy = psy;
mutex_unlock(&chip->lock);
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");

View File

@ -9,35 +9,27 @@
#ifndef __MAX77705_CHARGER_H
#define __MAX77705_CHARGER_H __FILE__
/* MAX77705_CHG_REG_CHG_INT */
#define MAX77705_BYP_I BIT(0)
#define MAX77705_INP_LIMIT_I BIT(1)
#define MAX77705_BATP_I BIT(2)
#define MAX77705_BAT_I BIT(3)
#define MAX77705_CHG_I BIT(4)
#define MAX77705_WCIN_I BIT(5)
#define MAX77705_CHGIN_I BIT(6)
#define MAX77705_AICL_I BIT(7)
#include <linux/regmap.h>
/* MAX77705_CHG_REG_CHG_INT_MASK */
#define MAX77705_BYP_IM BIT(0)
#define MAX77705_INP_LIMIT_IM BIT(1)
#define MAX77705_BATP_IM BIT(2)
#define MAX77705_BAT_IM BIT(3)
#define MAX77705_CHG_IM BIT(4)
#define MAX77705_WCIN_IM BIT(5)
#define MAX77705_CHGIN_IM BIT(6)
#define MAX77705_AICL_IM BIT(7)
/* MAX77705_CHG_REG_CHG_INT */
#define MAX77705_BYP_I (0)
#define MAX77705_INP_LIMIT_I (1)
#define MAX77705_BATP_I (2)
#define MAX77705_BAT_I (3)
#define MAX77705_CHG_I (4)
#define MAX77705_WCIN_I (5)
#define MAX77705_CHGIN_I (6)
#define MAX77705_AICL_I (7)
/* MAX77705_CHG_REG_CHG_INT_OK */
#define MAX77705_BYP_OK BIT(0)
#define MAX77705_DISQBAT_OK BIT(1)
#define MAX77705_BATP_OK BIT(2)
#define MAX77705_BAT_OK BIT(3)
#define MAX77705_CHG_OK BIT(4)
#define MAX77705_WCIN_OK BIT(5)
#define MAX77705_CHGIN_OK BIT(6)
#define MAX77705_AICL_OK BIT(7)
#define MAX77705_BYP_OK BIT(MAX77705_BYP_I)
#define MAX77705_DISQBAT_OK BIT(MAX77705_INP_LIMIT_I)
#define MAX77705_BATP_OK BIT(MAX77705_BATP_I)
#define MAX77705_BAT_OK BIT(MAX77705_BAT_I)
#define MAX77705_CHG_OK BIT(MAX77705_CHG_I)
#define MAX77705_WCIN_OK BIT(MAX77705_WCIN_I)
#define MAX77705_CHGIN_OK BIT(MAX77705_CHGIN_I)
#define MAX77705_AICL_OK BIT(MAX77705_AICL_I)
/* MAX77705_CHG_REG_DETAILS_00 */
#define MAX77705_BATP_DTLS BIT(0)
@ -63,7 +55,6 @@
#define MAX77705_BUCK_SHIFT 2
#define MAX77705_BOOST_SHIFT 3
#define MAX77705_WDTEN_SHIFT 4
#define MAX77705_MODE_MASK GENMASK(3, 0)
#define MAX77705_CHG_MASK BIT(MAX77705_CHG_SHIFT)
#define MAX77705_UNO_MASK BIT(MAX77705_UNO_SHIFT)
#define MAX77705_OTG_MASK BIT(MAX77705_OTG_SHIFT)
@ -74,34 +65,19 @@
#define MAX77705_OTG_CTRL (MAX77705_OTG_MASK | MAX77705_BOOST_MASK)
/* MAX77705_CHG_REG_CNFG_01 */
#define MAX77705_FCHGTIME_SHIFT 0
#define MAX77705_FCHGTIME_MASK GENMASK(2, 0)
#define MAX77705_CHG_RSTRT_SHIFT 4
#define MAX77705_CHG_RSTRT_MASK GENMASK(5, 4)
#define MAX77705_FCHGTIME_DISABLE 0
#define MAX77705_CHG_RSTRT_DISABLE 0x3
#define MAX77705_PQEN_SHIFT 7
#define MAX77705_PQEN_MASK BIT(7)
#define MAX77705_CHG_PQEN_DISABLE 0
#define MAX77705_CHG_PQEN_ENABLE 1
/* MAX77705_CHG_REG_CNFG_02 */
#define MAX77705_OTG_ILIM_SHIFT 6
#define MAX77705_OTG_ILIM_MASK GENMASK(7, 6)
#define MAX77705_OTG_ILIM_500 0
#define MAX77705_OTG_ILIM_900 1
#define MAX77705_OTG_ILIM_1200 2
#define MAX77705_OTG_ILIM_1500 3
#define MAX77705_CHG_CC GENMASK(5, 0)
/* MAX77705_CHG_REG_CNFG_03 */
#define MAX77705_TO_ITH_SHIFT 0
#define MAX77705_TO_ITH_MASK GENMASK(2, 0)
#define MAX77705_TO_TIME_SHIFT 3
#define MAX77705_TO_TIME_MASK GENMASK(5, 3)
#define MAX77705_SYS_TRACK_DIS_SHIFT 7
#define MAX77705_SYS_TRACK_DIS_MASK BIT(7)
#define MAX77705_TO_ITH_150MA 0
#define MAX77705_TO_TIME_30M 3
#define MAX77705_SYS_TRACK_ENABLE 0
@ -110,15 +86,8 @@
/* MAX77705_CHG_REG_CNFG_04 */
#define MAX77705_CHG_MINVSYS_SHIFT 6
#define MAX77705_CHG_MINVSYS_MASK GENMASK(7, 6)
#define MAX77705_CHG_PRM_SHIFT 0
#define MAX77705_CHG_PRM_MASK GENMASK(5, 0)
#define MAX77705_CHG_CV_PRM_SHIFT 0
#define MAX77705_CHG_CV_PRM_MASK GENMASK(5, 0)
/* MAX77705_CHG_REG_CNFG_05 */
#define MAX77705_REG_B2SOVRC_SHIFT 0
#define MAX77705_REG_B2SOVRC_MASK GENMASK(3, 0)
#define MAX77705_B2SOVRC_DISABLE 0
#define MAX77705_B2SOVRC_4_5A 6
#define MAX77705_B2SOVRC_4_8A 8
@ -128,9 +97,8 @@
#define MAX77705_WDTCLR_SHIFT 0
#define MAX77705_WDTCLR_MASK GENMASK(1, 0)
#define MAX77705_WDTCLR 1
#define MAX77705_CHGPROT_MASK GENMASK(3, 2)
#define MAX77705_CHGPROT_UNLOCKED GENMASK(3, 2)
#define MAX77705_SLOWEST_LX_SLOPE GENMASK(6, 5)
#define MAX77705_CHGPROT_UNLOCKED 3
#define MAX77705_SLOWEST_LX_SLOPE 3
/* MAX77705_CHG_REG_CNFG_07 */
#define MAX77705_CHG_FMBST 4
@ -140,36 +108,14 @@
#define MAX77705_REG_FGSRC_MASK BIT(MAX77705_REG_FGSRC_SHIFT)
/* MAX77705_CHG_REG_CNFG_08 */
#define MAX77705_REG_FSW_SHIFT 0
#define MAX77705_REG_FSW_MASK GENMASK(1, 0)
#define MAX77705_CHG_FSW_3MHz 0
#define MAX77705_CHG_FSW_2MHz 1
#define MAX77705_CHG_FSW_1_5MHz 2
/* MAX77705_CHG_REG_CNFG_09 */
#define MAX77705_CHG_CHGIN_LIM_MASK GENMASK(6, 0)
#define MAX77705_CHG_EN_MASK BIT(7)
#define MAX77705_CHG_DISABLE 0
#define MAX77705_CHARGER_CHG_CHARGING(_reg) \
(((_reg) & MAX77705_CHG_EN_MASK) > 1)
/* MAX77705_CHG_REG_CNFG_10 */
#define MAX77705_CHG_WCIN_LIM GENMASK(5, 0)
/* MAX77705_CHG_REG_CNFG_11 */
#define MAX77705_VBYPSET_SHIFT 0
#define MAX77705_VBYPSET_MASK GENMASK(6, 0)
/* MAX77705_CHG_REG_CNFG_12 */
#define MAX77705_CHGINSEL_SHIFT 5
#define MAX77705_CHGINSEL_MASK BIT(MAX77705_CHGINSEL_SHIFT)
#define MAX77705_WCINSEL_SHIFT 6
#define MAX77705_WCINSEL_MASK BIT(MAX77705_WCINSEL_SHIFT)
#define MAX77705_VCHGIN_REG_MASK GENMASK(4, 3)
#define MAX77705_WCIN_REG_MASK GENMASK(2, 1)
#define MAX77705_REG_DISKIP_SHIFT 0
#define MAX77705_REG_DISKIP_MASK BIT(MAX77705_REG_DISKIP_SHIFT)
/* REG=4.5V, UVLO=4.7V */
#define MAX77705_VCHGIN_4_5 0
/* REG=4.5V, UVLO=4.7V */
@ -183,9 +129,59 @@
#define MAX77705_CURRENT_CHGIN_MIN 100000
#define MAX77705_CURRENT_CHGIN_MAX 3200000
enum max77705_field_idx {
MAX77705_CHGPROT,
MAX77705_CHG_EN,
MAX77705_CHG_CC_LIM,
MAX77705_CHG_CHGIN_LIM,
MAX77705_CHG_CV_PRM,
MAX77705_CHG_PQEN,
MAX77705_CHG_RSTRT,
MAX77705_CHG_WCIN,
MAX77705_FCHGTIME,
MAX77705_LX_SLOPE,
MAX77705_MODE,
MAX77705_OTG_ILIM,
MAX77705_REG_B2SOVRC,
MAX77705_REG_DISKIP,
MAX77705_REG_FSW,
MAX77705_SYS_TRACK,
MAX77705_TO,
MAX77705_TO_TIME,
MAX77705_VBYPSET,
MAX77705_VCHGIN,
MAX77705_WCIN,
MAX77705_N_REGMAP_FIELDS,
};
static const struct reg_field max77705_reg_field[MAX77705_N_REGMAP_FIELDS] = {
[MAX77705_MODE] = REG_FIELD(MAX77705_CHG_REG_CNFG_00, 0, 3),
[MAX77705_FCHGTIME] = REG_FIELD(MAX77705_CHG_REG_CNFG_01, 0, 2),
[MAX77705_CHG_RSTRT] = REG_FIELD(MAX77705_CHG_REG_CNFG_01, 4, 5),
[MAX77705_CHG_PQEN] = REG_FIELD(MAX77705_CHG_REG_CNFG_01, 7, 7),
[MAX77705_CHG_CC_LIM] = REG_FIELD(MAX77705_CHG_REG_CNFG_02, 0, 5),
[MAX77705_OTG_ILIM] = REG_FIELD(MAX77705_CHG_REG_CNFG_02, 6, 7),
[MAX77705_TO] = REG_FIELD(MAX77705_CHG_REG_CNFG_03, 0, 2),
[MAX77705_TO_TIME] = REG_FIELD(MAX77705_CHG_REG_CNFG_03, 3, 5),
[MAX77705_SYS_TRACK] = REG_FIELD(MAX77705_CHG_REG_CNFG_03, 7, 7),
[MAX77705_CHG_CV_PRM] = REG_FIELD(MAX77705_CHG_REG_CNFG_04, 0, 5),
[MAX77705_REG_B2SOVRC] = REG_FIELD(MAX77705_CHG_REG_CNFG_05, 0, 3),
[MAX77705_CHGPROT] = REG_FIELD(MAX77705_CHG_REG_CNFG_06, 2, 3),
[MAX77705_LX_SLOPE] = REG_FIELD(MAX77705_CHG_REG_CNFG_06, 5, 6),
[MAX77705_REG_FSW] = REG_FIELD(MAX77705_CHG_REG_CNFG_08, 0, 1),
[MAX77705_CHG_CHGIN_LIM] = REG_FIELD(MAX77705_CHG_REG_CNFG_09, 0, 6),
[MAX77705_CHG_EN] = REG_FIELD(MAX77705_CHG_REG_CNFG_09, 7, 7),
[MAX77705_CHG_WCIN] = REG_FIELD(MAX77705_CHG_REG_CNFG_10, 0, 5),
[MAX77705_VBYPSET] = REG_FIELD(MAX77705_CHG_REG_CNFG_11, 0, 6),
[MAX77705_REG_DISKIP] = REG_FIELD(MAX77705_CHG_REG_CNFG_12, 0, 0),
[MAX77705_WCIN] = REG_FIELD(MAX77705_CHG_REG_CNFG_12, 1, 2),
[MAX77705_VCHGIN] = REG_FIELD(MAX77705_CHG_REG_CNFG_12, 3, 4),
};
struct max77705_charger_data {
struct device *dev;
struct regmap *regmap;
struct regmap_field *rfield[MAX77705_N_REGMAP_FIELDS];
struct power_supply_battery_info *bat_info;
struct workqueue_struct *wqueue;
struct work_struct chgin_work;

View File

@ -176,6 +176,8 @@ enum power_supply_property {
POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
POWER_SUPPLY_PROP_MANUFACTURE_DAY,
POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
POWER_SUPPLY_PROP_STATE_OF_HEALTH,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,