|
@@ -34,6 +34,7 @@
|
|
|
#include <linux/dmi.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/suspend.h>
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
|
|
#ifdef CONFIG_ACPI_PROCFS_POWER
|
|
|
#include <linux/proc_fs.h>
|
|
@@ -95,6 +96,18 @@ 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,
|
|
|
};
|
|
|
|
|
|
struct acpi_battery {
|
|
@@ -438,6 +451,21 @@ static int acpi_battery_get_info(struct acpi_battery *battery)
|
|
|
kfree(buffer.pointer);
|
|
|
if (test_bit(ACPI_BATTERY_QUIRK_PERCENTAGE_CAPACITY, &battery->flags))
|
|
|
battery->full_charge_capacity = battery->design_capacity;
|
|
|
+ if (test_bit(ACPI_BATTERY_QUIRK_THINKPAD_MAH, &battery->flags) &&
|
|
|
+ battery->power_unit && battery->design_voltage) {
|
|
|
+ battery->design_capacity = battery->design_capacity *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ battery->full_charge_capacity = battery->full_charge_capacity *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ battery->design_capacity_warning =
|
|
|
+ battery->design_capacity_warning *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ /* Curiously, design_capacity_low, unlike the rest of them,
|
|
|
+ is correct. */
|
|
|
+ /* capacity_granularity_* equal 1 on the systems tested, so
|
|
|
+ it's impossible to tell if they would need an adjustment
|
|
|
+ or not if their values were higher. */
|
|
|
+ }
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -486,6 +514,11 @@ static int acpi_battery_get_state(struct acpi_battery *battery)
|
|
|
&& battery->capacity_now >= 0 && battery->capacity_now <= 100)
|
|
|
battery->capacity_now = (battery->capacity_now *
|
|
|
battery->full_charge_capacity) / 100;
|
|
|
+ if (test_bit(ACPI_BATTERY_QUIRK_THINKPAD_MAH, &battery->flags) &&
|
|
|
+ battery->power_unit && battery->design_voltage) {
|
|
|
+ battery->capacity_now = battery->capacity_now *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ }
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -595,6 +628,24 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
|
|
|
mutex_unlock(&battery->sysfs_lock);
|
|
|
}
|
|
|
|
|
|
+static void find_battery(const struct dmi_header *dm, void *private)
|
|
|
+{
|
|
|
+ struct acpi_battery *battery = (struct acpi_battery *)private;
|
|
|
+ /* Note: the hardcoded offsets below have been extracted from
|
|
|
+ the source code of dmidecode. */
|
|
|
+ if (dm->type == DMI_ENTRY_PORTABLE_BATTERY && dm->length >= 8) {
|
|
|
+ const u8 *dmi_data = (const u8 *)(dm + 1);
|
|
|
+ int dmi_capacity = get_unaligned((const u16 *)(dmi_data + 6));
|
|
|
+ if (dm->length >= 18)
|
|
|
+ dmi_capacity *= dmi_data[17];
|
|
|
+ if (battery->design_capacity * battery->design_voltage / 1000
|
|
|
+ != dmi_capacity &&
|
|
|
+ battery->design_capacity * 10 == dmi_capacity)
|
|
|
+ set_bit(ACPI_BATTERY_QUIRK_THINKPAD_MAH,
|
|
|
+ &battery->flags);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* According to the ACPI spec, some kinds of primary batteries can
|
|
|
* report percentage battery remaining capacity directly to OS.
|
|
@@ -620,6 +671,32 @@ static void acpi_battery_quirks(struct acpi_battery *battery)
|
|
|
battery->capacity_now = (battery->capacity_now *
|
|
|
battery->full_charge_capacity) / 100;
|
|
|
}
|
|
|
+
|
|
|
+ if (test_bit(ACPI_BATTERY_QUIRK_THINKPAD_MAH, &battery->flags))
|
|
|
+ return ;
|
|
|
+
|
|
|
+ if (battery->power_unit && dmi_name_in_vendors("LENOVO")) {
|
|
|
+ const char *s;
|
|
|
+ s = dmi_get_system_info(DMI_PRODUCT_VERSION);
|
|
|
+ if (s && !strnicmp(s, "ThinkPad", 8)) {
|
|
|
+ dmi_walk(find_battery, battery);
|
|
|
+ if (test_bit(ACPI_BATTERY_QUIRK_THINKPAD_MAH,
|
|
|
+ &battery->flags) &&
|
|
|
+ battery->design_voltage) {
|
|
|
+ battery->design_capacity =
|
|
|
+ battery->design_capacity *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ battery->full_charge_capacity =
|
|
|
+ battery->full_charge_capacity *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ battery->design_capacity_warning =
|
|
|
+ battery->design_capacity_warning *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ battery->capacity_now = battery->capacity_now *
|
|
|
+ 10000 / battery->design_voltage;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static int acpi_battery_update(struct acpi_battery *battery)
|