// SPDX-License-Identifier: GPL-2.0-or-later
/*
* battery.c - ACPI Battery Driver (Revision: 2.0)
*
* Copyright (C) 2007 Alexey Starikovskiy <astarikovskiy@suse.de>
* Copyright (C) 2004-2007 Vladimir Lebedev <vladimir.p.lebedev@intel.com>
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/async.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/types.h>
#include <asm/unaligned.h>
#ifdef CONFIG_ACPI_PROCFS_POWER
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#endif
#include <linux/acpi.h>
#include <linux/power_supply.h>
#include <acpi/battery.h>
#define PREFIX "ACPI: "
#define ACPI_BATTERY_VALUE_UNKNOWN 0xFFFFFFFF
#define ACPI_BATTERY_DEVICE_NAME "Battery"
/* Battery power unit: 0 means mW, 1 means mA */
#define ACPI_BATTERY_POWER_UNIT_MA 1
#define ACPI_BATTERY_STATE_DISCHARGING 0x1
#define ACPI_BATTERY_STATE_CHARGING 0x2
#define ACPI_BATTERY_STATE_CRITICAL 0x4
#define _COMPONENT ACPI_BATTERY_COMPONENT
ACPI_MODULE_NAME("battery");
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
MODULE_DESCRIPTION("ACPI Battery Driver");
MODULE_LICENSE("GPL");
static async_cookie_t async_cookie;
static bool battery_driver_registered;
static int battery_bix_broken_package;
static int battery_notification_delay_ms;
static int battery_ac_is_broken;
static int battery_check_pmic = 1;
static unsigned int cache_time = 1000;
module_param(cache_time, uint, 0644);
MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
#ifdef CONFIG_ACPI_PROCFS_POWER
extern struct proc_dir_entry *acpi_lock_battery_dir(void);
extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir);
#endif
static const struct acpi_device_id battery_device_ids[] = {
{"PNP0C0A", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, battery_device_ids);
/* Lists of PMIC ACPI HIDs with an (often better) native battery driver */
static const char * const acpi_battery_blacklist[] = {
"INT33F4", /* X-Powers AXP288 PMIC */
};
enum {
ACPI_BATTERY_ALARM_PRESENT,
ACPI_BATTERY_XINFO_PRESENT,
ACPI_BATTERY_QUIRK_PERCENTAGE_CAPACITY,
/* On Lenovo Thinkpad models from 2010 and 2011, the power unit
switches between mWh and mAh depending on whether the system
is running on battery or not. When mAh is the unit, most
reported values are incorrect and need to be adjusted by
10000/design_voltage. Verified on x201, t410, t410s, and x220.
Pre-2010 and 2012 models appear to always report in mWh and
are thus unaffected (tested with t42, t61, t500, x200, x300,
and x230). Also, in mid-2012 Lenovo issued a BIOS update for
the 2011 models that fixes the issue (tested on x220 with a
post-1.29 BIOS), but as of Nov. 2012, no such update is
available for the 2010 models. */
ACPI_BATTERY_QUIRK_THINKPAD_MAH,
/* for batteries reporting current capacity with design capacity
* on a full charge, but showing degradation in full charge cap.
*/
ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE,
};
struct acpi_battery {
struct mutex lock;
struct mutex sysfs_lock;
struct power_supply *bat;
struct power_supply_desc bat_desc;
struct acpi_device *device;
struct notifier_block pm_nb;
struct list_head list;
unsigned long update_time;
int revision;
int rate_now;
int capacity_now;
int voltage_now;
int design_capacity;
int full_charge_capacity;
int technology;
int design_voltage;
int design_capacity_warning;
int design_capacity_low;
int cycle_count;
int measurement_accuracy;
int max_sampling_time;
int min_sampling_time;
int max_averaging_interval;
int min_averaging_interval;
int capacity_granularity_1;
int capacity_granularity_2;
int alarm;
char model_number[32];
char serial_number[32];
char type[32];
char oem_info[32];
int state;
int power_unit;
unsigned long flags;
};
#define to_acpi_battery(x) power_supply_get_drvdata(x)
static inline int acpi_battery_present(struct acpi_battery *battery)
{
return battery->device->status.battery_present;
}
static int acpi_battery_technology(struct acpi_battery *battery)
{
if (!strcasecmp("NiCd", battery->type))
return POWER_SUPPLY_TECHNOLOGY_NiCd;
if (!strcasecmp("NiMH", battery->type))
return POWER_SUPPLY_TECHNOLOGY_NiMH;
if (!strcasecmp("LION", battery->type))
return POWER_SUPPLY_TECHNOLOGY_LION;
if (!strncasecmp("LI-ION", battery->type, 6))
return POWER_SUPPLY_TECHNOLOGY_LION;
if (!strcasecmp("LiP", battery->type))
return POWER_SUPPLY_TECHNOLOGY_LIPO;
return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
}
static int acpi_battery_get_state(struct acpi_battery *battery);
static int acpi_battery_is_charged(struct acpi_battery *battery)
{
/* charging, discharging or critical low */
if (battery->state != 0)
return 0;
/* battery not reporting charge */
if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN ||
battery->capacity_now == 0)
return 0;
/* good batteries update full_charge as the batteries degrade */
if (battery->full_charge_capacity == battery->capacity_now)
return 1;
/* fallback to using design values for broken batteries */
if (battery->design_capacity == battery->capacity_now)
return 1;
/* we don't do any sort of metric based on percentages */
return 0;
}
static bool acpi_battery_is_degraded(struct acpi_battery *battery)
{
return battery->full_charge_capacity && battery->design_capacity &&
battery->full_charge_capacity < battery->design_capacity;
}
static int acpi_battery_handle_discharging(struct acpi_battery *battery)
{
/*
* Some devices wrongly report discharging if the battery's charge level
* was above the device's start charging threshold atm the AC adapter
* was plugged in and the device thus did not start a new charge cycle.
*/
if ((battery_ac_is_broken || power_supply_is_system_supplied()) &&
battery->rate_now == 0)
return POWER_SUPPLY_STATUS_NOT_CHARGING;
return POWER_SUPPLY_STATUS_DISCHARGING;
}
static int acpi_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret = 0;
struct acpi_battery *battery = to_acpi_battery(psy);
if (acpi_battery_present(battery)) {
/* run battery update only if it is present */
acpi_battery_get_state(battery);
} else if (psp != POWER_SUPPLY_PROP_PRESENT)
return -ENODEV;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (battery->state & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = acpi_battery_handle_discharging(battery);
else if (battery->state & ACPI_BATTERY_STATE_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (acpi_battery_is_charged(battery))
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = acpi_battery_present(battery);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = acpi_bat
|