diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-bus-acpi | 37 | ||||
| -rw-r--r-- | drivers/acpi/ac.c | 2 | ||||
| -rw-r--r-- | drivers/acpi/battery.c | 174 | ||||
| -rw-r--r-- | drivers/acpi/battery.h | 11 | ||||
| -rw-r--r-- | drivers/acpi/pmic/tps68470_pmic.c | 10 | ||||
| -rw-r--r-- | drivers/acpi/sbs.c | 2 | ||||
| -rw-r--r-- | drivers/platform/x86/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 389 | ||||
| -rw-r--r-- | drivers/power/supply/ds2780_battery.c | 5 | ||||
| -rw-r--r-- | drivers/power/supply/ds2781_battery.c | 5 | ||||
| -rw-r--r-- | drivers/power/supply/power_supply_core.c | 2 | ||||
| -rw-r--r-- | include/acpi/battery.h | 21 | ||||
| -rw-r--r-- | include/linux/power_supply.h | 2 | 
13 files changed, 624 insertions, 37 deletions
| diff --git a/Documentation/ABI/testing/sysfs-bus-acpi b/Documentation/ABI/testing/sysfs-bus-acpi index 7fa9cbc75344..e7898cfe5fb1 100644 --- a/Documentation/ABI/testing/sysfs-bus-acpi +++ b/Documentation/ABI/testing/sysfs-bus-acpi @@ -56,3 +56,40 @@ Description:  		Writing 1 to this attribute will trigger hot removal of  		this device object.  This file exists for every device  		object that has _EJ0 method. + +What:		/sys/bus/acpi/devices/.../status +Date:		Jan, 2014 +Contact:	Rafael J. Wysocki <rjw@rjwysocki.net> +Description: +		(RO) Returns the ACPI device status: enabled, disabled or +		functioning or present, if the method _STA is present. + +		The return value is a decimal integer representing the device's +		status bitmap: + +		Bit [0] –  Set if the device is present. +		Bit [1] –  Set if the device is enabled and decoding its +		           resources. +		Bit [2] –  Set if the device should be shown in the UI. +		Bit [3] –  Set if the device is functioning properly (cleared if +		           device failed its diagnostics). +		Bit [4] –  Set if the battery is present. +		Bits [31:5] –  Reserved (must be cleared) + +		If bit [0] is clear, then bit 1 must also be clear (a device +		that is not present cannot be enabled). + +		Bit 0 can be clear (not present) with bit [3] set (device is +		functional).  This case is used to indicate a valid device for +		which no device driver should be loaded. + +		More special cases are covered in the ACPI specification. + +What:		/sys/bus/acpi/devices/.../hrv +Date:		Apr, 2016 +Contact:	Rafael J. Wysocki <rjw@rjwysocki.net> +Description: +		(RO) Allows users to read the hardware version of non-PCI +		hardware, if the _HRV control method is present.  It is mostly +		useful for non-PCI devices because lspci can list the hardware +		version for PCI devices. diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 47a7ed557bd6..2d8de2f8c1ed 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -33,7 +33,7 @@  #include <linux/platform_device.h>  #include <linux/power_supply.h>  #include <linux/acpi.h> -#include "battery.h" +#include <acpi/battery.h>  #define PREFIX "ACPI: " diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f2eb6c37ea0a..bdb24d636d9a 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -21,8 +21,12 @@   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h> +#include <linux/list.h>  #include <linux/module.h> +#include <linux/mutex.h>  #include <linux/init.h>  #include <linux/types.h>  #include <linux/jiffies.h> @@ -42,7 +46,7 @@  #include <linux/acpi.h>  #include <linux/power_supply.h> -#include "battery.h" +#include <acpi/battery.h>  #define PREFIX "ACPI: " @@ -115,6 +119,10 @@ enum {  	   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 { @@ -124,6 +132,7 @@ struct acpi_battery {  	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; @@ -200,6 +209,12 @@ static int acpi_battery_is_charged(struct acpi_battery *battery)  	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_get_property(struct power_supply *psy,  				     enum power_supply_property psp,  				     union power_supply_propval *val) @@ -471,6 +486,10 @@ static int extract_battery_info(const int use_bix,  		   it's impossible to tell if they would need an adjustment  		   or not if their values were higher.  */  	} +	if (test_bit(ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE, &battery->flags) && +	    battery->capacity_now > battery->full_charge_capacity) +		battery->capacity_now = battery->full_charge_capacity; +  	return result;  } @@ -563,6 +582,10 @@ static int acpi_battery_get_state(struct acpi_battery *battery)  		battery->capacity_now = battery->capacity_now *  		    10000 / battery->design_voltage;  	} +	if (test_bit(ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE, &battery->flags) && +	    battery->capacity_now > battery->full_charge_capacity) +		battery->capacity_now = battery->full_charge_capacity; +  	return result;  } @@ -626,6 +649,139 @@ static const struct device_attribute alarm_attr = {  	.store = acpi_battery_alarm_store,  }; +/* + * The Battery Hooking API + * + * This API is used inside other drivers that need to expose + * platform-specific behaviour within the generic driver in a + * generic way. + * + */ + +static LIST_HEAD(acpi_battery_list); +static LIST_HEAD(battery_hook_list); +static DEFINE_MUTEX(hook_mutex); + +static void __battery_hook_unregister(struct acpi_battery_hook *hook, int lock) +{ +	struct acpi_battery *battery; +	/* +	 * In order to remove a hook, we first need to +	 * de-register all the batteries that are registered. +	 */ +	if (lock) +		mutex_lock(&hook_mutex); +	list_for_each_entry(battery, &acpi_battery_list, list) { +		hook->remove_battery(battery->bat); +	} +	list_del(&hook->list); +	if (lock) +		mutex_unlock(&hook_mutex); +	pr_info("extension unregistered: %s\n", hook->name); +} + +void battery_hook_unregister(struct acpi_battery_hook *hook) +{ +	__battery_hook_unregister(hook, 1); +} +EXPORT_SYMBOL_GPL(battery_hook_unregister); + +void battery_hook_register(struct acpi_battery_hook *hook) +{ +	struct acpi_battery *battery; + +	mutex_lock(&hook_mutex); +	INIT_LIST_HEAD(&hook->list); +	list_add(&hook->list, &battery_hook_list); +	/* +	 * Now that the driver is registered, we need +	 * to notify the hook that a battery is available +	 * for each battery, so that the driver may add +	 * its attributes. +	 */ +	list_for_each_entry(battery, &acpi_battery_list, list) { +		if (hook->add_battery(battery->bat)) { +			/* +			 * If a add-battery returns non-zero, +			 * the registration of the extension has failed, +			 * and we will not add it to the list of loaded +			 * hooks. +			 */ +			pr_err("extension failed to load: %s", hook->name); +			__battery_hook_unregister(hook, 0); +			return; +		} +	} +	pr_info("new extension: %s\n", hook->name); +	mutex_unlock(&hook_mutex); +} +EXPORT_SYMBOL_GPL(battery_hook_register); + +/* + * This function gets called right after the battery sysfs + * attributes have been added, so that the drivers that + * define custom sysfs attributes can add their own. +*/ +static void battery_hook_add_battery(struct acpi_battery *battery) +{ +	struct acpi_battery_hook *hook_node; + +	mutex_lock(&hook_mutex); +	INIT_LIST_HEAD(&battery->list); +	list_add(&battery->list, &acpi_battery_list); +	/* +	 * Since we added a new battery to the list, we need to +	 * iterate over the hooks and call add_battery for each +	 * hook that was registered. This usually happens +	 * when a battery gets hotplugged or initialized +	 * during the battery module initialization. +	 */ +	list_for_each_entry(hook_node, &battery_hook_list, list) { +		if (hook_node->add_battery(battery->bat)) { +			/* +			 * The notification of the extensions has failed, to +			 * prevent further errors we will unload the extension. +			 */ +			__battery_hook_unregister(hook_node, 0); +			pr_err("error in extension, unloading: %s", +					hook_node->name); +		} +	} +	mutex_unlock(&hook_mutex); +} + +static void battery_hook_remove_battery(struct acpi_battery *battery) +{ +	struct acpi_battery_hook *hook; + +	mutex_lock(&hook_mutex); +	/* +	 * Before removing the hook, we need to remove all +	 * custom attributes from the battery. +	 */ +	list_for_each_entry(hook, &battery_hook_list, list) { +		hook->remove_battery(battery->bat); +	} +	/* Then, just remove the battery from the list */ +	list_del(&battery->list); +	mutex_unlock(&hook_mutex); +} + +static void __exit battery_hook_exit(void) +{ +	struct acpi_battery_hook *hook; +	struct acpi_battery_hook *ptr; +	/* +	 * At this point, the acpi_bus_unregister_driver() +	 * has called remove for all batteries. We just +	 * need to remove the hooks. +	 */ +	list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +		__battery_hook_unregister(hook, 1); +	} +	mutex_destroy(&hook_mutex); +} +  static int sysfs_add_battery(struct acpi_battery *battery)  {  	struct power_supply_config psy_cfg = { .drv_data = battery, }; @@ -653,6 +809,7 @@ static int sysfs_add_battery(struct acpi_battery *battery)  		battery->bat = NULL;  		return result;  	} +	battery_hook_add_battery(battery);  	return device_create_file(&battery->bat->dev, &alarm_attr);  } @@ -663,7 +820,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery)  		mutex_unlock(&battery->sysfs_lock);  		return;  	} - +	battery_hook_remove_battery(battery);  	device_remove_file(&battery->bat->dev, &alarm_attr);  	power_supply_unregister(battery->bat);  	battery->bat = NULL; @@ -739,6 +896,15 @@ static void acpi_battery_quirks(struct acpi_battery *battery)  			}  		}  	} + +	if (test_bit(ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE, &battery->flags)) +		return; + +	if (acpi_battery_is_degraded(battery) && +	    battery->capacity_now > battery->full_charge_capacity) { +		set_bit(ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE, &battery->flags); +		battery->capacity_now = battery->full_charge_capacity; +	}  }  static int acpi_battery_update(struct acpi_battery *battery, bool resume) @@ -1357,8 +1523,10 @@ static int __init acpi_battery_init(void)  static void __exit acpi_battery_exit(void)  {  	async_synchronize_cookie(async_cookie + 1); -	if (battery_driver_registered) +	if (battery_driver_registered) {  		acpi_bus_unregister_driver(&acpi_battery_driver); +		battery_hook_exit(); +	}  #ifdef CONFIG_ACPI_PROCFS_POWER  	if (acpi_battery_dir)  		acpi_unlock_battery_dir(acpi_battery_dir); diff --git a/drivers/acpi/battery.h b/drivers/acpi/battery.h deleted file mode 100644 index 225f493d4c27..000000000000 --- a/drivers/acpi/battery.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __ACPI_BATTERY_H -#define __ACPI_BATTERY_H - -#define ACPI_BATTERY_CLASS "battery" - -#define ACPI_BATTERY_NOTIFY_STATUS	0x80 -#define ACPI_BATTERY_NOTIFY_INFO	0x81 -#define ACPI_BATTERY_NOTIFY_THRESHOLD   0x82 - -#endif diff --git a/drivers/acpi/pmic/tps68470_pmic.c b/drivers/acpi/pmic/tps68470_pmic.c index 7f3c567e8168..a083de507009 100644 --- a/drivers/acpi/pmic/tps68470_pmic.c +++ b/drivers/acpi/pmic/tps68470_pmic.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0  /*   * TI TPS68470 PMIC operation region driver   * @@ -5,15 +6,6 @@   *   * Author: Rajmohan Mani <rajmohan.mani@intel.com>   * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License version - * 2 as published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - *   * Based on drivers/acpi/pmic/intel_pmic* drivers   */ diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index a2428e9462dd..295b59271189 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -32,9 +32,9 @@  #include <linux/delay.h>  #include <linux/power_supply.h>  #include <linux/platform_data/x86/apple.h> +#include <acpi/battery.h>  #include "sbshc.h" -#include "battery.h"  #define PREFIX "ACPI: " diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 51ebc5a6053f..ef016e46544a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -439,6 +439,7 @@ config SURFACE3_WMI  config THINKPAD_ACPI  	tristate "ThinkPad ACPI Laptop Extras"  	depends on ACPI +	depends on ACPI_BATTERY  	depends on INPUT  	depends on RFKILL || RFKILL = n  	depends on ACPI_VIDEO || ACPI_VIDEO = n diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index d5eaf3b1edba..1c57ee2b6d19 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -23,7 +23,7 @@  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TPACPI_VERSION "0.25" +#define TPACPI_VERSION "0.26"  #define TPACPI_SYSFS_VERSION 0x030000  /* @@ -66,6 +66,7 @@  #include <linux/seq_file.h>  #include <linux/sysfs.h>  #include <linux/backlight.h> +#include <linux/bitops.h>  #include <linux/fb.h>  #include <linux/platform_device.h>  #include <linux/hwmon.h> @@ -78,11 +79,13 @@  #include <linux/workqueue.h>  #include <linux/acpi.h>  #include <linux/pci_ids.h> +#include <linux/power_supply.h>  #include <linux/thinkpad_acpi.h>  #include <sound/core.h>  #include <sound/control.h>  #include <sound/initval.h>  #include <linux/uaccess.h> +#include <acpi/battery.h>  #include <acpi/video.h>  /* ThinkPad CMOS commands */ @@ -335,6 +338,7 @@ static struct {  	u32 sensors_pdev_attrs_registered:1;  	u32 hotkey_poll_active:1;  	u32 has_adaptive_kbd:1; +	u32 battery:1;  } tp_features;  static struct { @@ -9209,6 +9213,385 @@ static struct ibm_struct mute_led_driver_data = {  	.resume = mute_led_resume,  }; +/* + * Battery Wear Control Driver + * Contact: Ognjen Galic <smclt30p@gmail.com> + */ + +/* Metadata */ + +#define GET_START	"BCTG" +#define SET_START	"BCCS" +#define GET_STOP	"BCSG" +#define SET_STOP	"BCSS" + +#define START_ATTR "charge_start_threshold" +#define STOP_ATTR  "charge_stop_threshold" + +enum { +	BAT_ANY = 0, +	BAT_PRIMARY = 1, +	BAT_SECONDARY = 2 +}; + +enum { +	/* Error condition bit */ +	METHOD_ERR = BIT(31), +}; + +enum { +	/* This is used in the get/set helpers */ +	THRESHOLD_START, +	THRESHOLD_STOP, +}; + +struct tpacpi_battery_data { +	int charge_start; +	int start_support; +	int charge_stop; +	int stop_support; +}; + +struct tpacpi_battery_driver_data { +	struct tpacpi_battery_data batteries[3]; +	int individual_addressing; +}; + +static struct tpacpi_battery_driver_data battery_info; + +/* ACPI helpers/functions/probes */ + +/** + * This evaluates a ACPI method call specific to the battery + * ACPI extension. The specifics are that an error is marked + * in the 32rd bit of the response, so we just check that here. + */ +static acpi_status tpacpi_battery_acpi_eval(char *method, int *ret, int param) +{ +	int response; + +	if (!acpi_evalf(hkey_handle, &response, method, "dd", param)) { +		acpi_handle_err(hkey_handle, "%s: evaluate failed", method); +		return AE_ERROR; +	} +	if (response & METHOD_ERR) { +		acpi_handle_err(hkey_handle, +				"%s evaluated but flagged as error", method); +		return AE_ERROR; +	} +	*ret = response; +	return AE_OK; +} + +static int tpacpi_battery_get(int what, int battery, int *ret) +{ +	switch (what) { +	case THRESHOLD_START: +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, ret, battery)) +			return -ENODEV; + +		/* The value is in the low 8 bits of the response */ +		*ret = *ret & 0xFF; +		return 0; +	case THRESHOLD_STOP: +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, ret, battery)) +			return -ENODEV; +		/* Value is in lower 8 bits */ +		*ret = *ret & 0xFF; +		/* +		 * On the stop value, if we return 0 that +		 * does not make any sense. 0 means Default, which +		 * means that charging stops at 100%, so we return +		 * that. +		 */ +		if (*ret == 0) +			*ret = 100; +		return 0; +	default: +		pr_crit("wrong parameter: %d", what); +		return -EINVAL; +	} +} + +static int tpacpi_battery_set(int what, int battery, int value) +{ +	int param, ret; +	/* The first 8 bits are the value of the threshold */ +	param = value; +	/* The battery ID is in bits 8-9, 2 bits */ +	param |= battery << 8; + +	switch (what) { +	case THRESHOLD_START: +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_START, &ret, param)) { +			pr_err("failed to set charge threshold on battery %d", +					battery); +			return -ENODEV; +		} +		return 0; +	case THRESHOLD_STOP: +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_STOP, &ret, param)) { +			pr_err("failed to set stop threshold: %d", battery); +			return -ENODEV; +		} +		return 0; +	default: +		pr_crit("wrong parameter: %d", what); +		return -EINVAL; +	} +} + +static int tpacpi_battery_probe(int battery) +{ +	int ret = 0; + +	memset(&battery_info, 0, sizeof(struct tpacpi_battery_driver_data)); +	/* +	 * 1) Get the current start threshold +	 * 2) Check for support +	 * 3) Get the current stop threshold +	 * 4) Check for support +	 */ +	if (acpi_has_method(hkey_handle, GET_START)) { +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, &ret, battery)) { +			pr_err("Error probing battery %d\n", battery); +			return -ENODEV; +		} +		/* Individual addressing is in bit 9 */ +		if (ret & BIT(9)) +			battery_info.individual_addressing = true; +		/* Support is marked in bit 8 */ +		if (ret & BIT(8)) +			battery_info.batteries[battery].start_support = 1; +		else +			return -ENODEV; +		if (tpacpi_battery_get(THRESHOLD_START, battery, +			&battery_info.batteries[battery].charge_start)) { +			pr_err("Error probing battery %d\n", battery); +			return -ENODEV; +		} +	} +	if (acpi_has_method(hkey_handle, GET_STOP)) { +		if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, &ret, battery)) { +			pr_err("Error probing battery stop; %d\n", battery); +			return -ENODEV; +		} +		/* Support is marked in bit 8 */ +		if (ret & BIT(8)) +			battery_info.batteries[battery].stop_support = 1; +		else +			return -ENODEV; +		if (tpacpi_battery_get(THRESHOLD_STOP, battery, +			&battery_info.batteries[battery].charge_stop)) { +			pr_err("Error probing battery stop: %d\n", battery); +			return -ENODEV; +		} +	} +	pr_info("battery %d registered (start %d, stop %d)", +			battery, +			battery_info.batteries[battery].charge_start, +			battery_info.batteries[battery].charge_stop); + +	return 0; +} + +/* General helper functions */ + +static int tpacpi_battery_get_id(const char *battery_name) +{ + +	if (strcmp(battery_name, "BAT0") == 0) +		return BAT_PRIMARY; +	if (strcmp(battery_name, "BAT1") == 0) +		return BAT_SECONDARY; +	/* +	 * If for some reason the battery is not BAT0 nor is it +	 * BAT1, we will assume it's the default, first battery, +	 * AKA primary. +	 */ +	pr_warn("unknown battery %s, assuming primary", battery_name); +	return BAT_PRIMARY; +} + +/* sysfs interface */ + +static ssize_t tpacpi_battery_store(int what, +				    struct device *dev, +				    const char *buf, size_t count) +{ +	struct power_supply *supply = to_power_supply(dev); +	unsigned long value; +	int battery, rval; +	/* +	 * Some systems have support for more than +	 * one battery. If that is the case, +	 * tpacpi_battery_probe marked that addressing +	 * them individually is supported, so we do that +	 * based on the device struct. +	 * +	 * On systems that are not supported, we assume +	 * the primary as most of the ACPI calls fail +	 * with "Any Battery" as the parameter. +	 */ +	if (battery_info.individual_addressing) +		/* BAT_PRIMARY or BAT_SECONDARY */ +		battery = tpacpi_battery_get_id(supply->desc->name); +	else +		battery = BAT_PRIMARY; + +	rval = kstrtoul(buf, 10, &value); +	if (rval) +		return rval; + +	switch (what) { +	case THRESHOLD_START: +		if (!battery_info.batteries[battery].start_support) +			return -ENODEV; +		/* valid values are [0, 99] */ +		if (value < 0 || value > 99) +			return -EINVAL; +		if (value > battery_info.batteries[battery].charge_stop) +			return -EINVAL; +		if (tpacpi_battery_set(THRESHOLD_START, battery, value)) +			return -ENODEV; +		battery_info.batteries[battery].charge_start = value; +		return count; + +	case THRESHOLD_STOP: +		if (!battery_info.batteries[battery].stop_support) +			return -ENODEV; +		/* valid values are [1, 100] */ +		if (value < 1 || value > 100) +			return -EINVAL; +		if (value < battery_info.batteries[battery].charge_start) +			return -EINVAL; +		battery_info.batteries[battery].charge_stop = value; +		/* +		 * When 100 is passed to stop, we need to flip +		 * it to 0 as that the EC understands that as +		 * "Default", which will charge to 100% +		 */ +		if (value == 100) +			value = 0; +		if (tpacpi_battery_set(THRESHOLD_STOP, battery, value)) +			return -EINVAL; +		return count; +	default: +		pr_crit("Wrong parameter: %d", what); +		return -EINVAL; +	} +	return count; +} + +static ssize_t tpacpi_battery_show(int what, +				   struct device *dev, +				   char *buf) +{ +	struct power_supply *supply = to_power_supply(dev); +	int ret, battery; +	/* +	 * Some systems have support for more than +	 * one battery. If that is the case, +	 * tpacpi_battery_probe marked that addressing +	 * them individually is supported, so we; +	 * based on the device struct. +	 * +	 * On systems that are not supported, we assume +	 * the primary as most of the ACPI calls fail +	 * with "Any Battery" as the parameter. +	 */ +	if (battery_info.individual_addressing) +		/* BAT_PRIMARY or BAT_SECONDARY */ +		battery = tpacpi_battery_get_id(supply->desc->name); +	else +		battery = BAT_PRIMARY; +	if (tpacpi_battery_get(what, battery, &ret)) +		return -ENODEV; +	return sprintf(buf, "%d\n", ret); +} + +static ssize_t charge_start_threshold_show(struct device *device, +				struct device_attribute *attr, +				char *buf) +{ +	return tpacpi_battery_show(THRESHOLD_START, device, buf); +} + +static ssize_t charge_stop_threshold_show(struct device *device, +				struct device_attribute *attr, +				char *buf) +{ +	return tpacpi_battery_show(THRESHOLD_STOP, device, buf); +} + +static ssize_t charge_start_threshold_store(struct device *dev, +				struct device_attribute *attr, +				const char *buf, size_t count) +{ +	return tpacpi_battery_store(THRESHOLD_START, dev, buf, count); +} + +static ssize_t charge_stop_threshold_store(struct device *dev, +				struct device_attribute *attr, +				const char *buf, size_t count) +{ +	return tpacpi_battery_store(THRESHOLD_STOP, dev, buf, count); +} + +static DEVICE_ATTR_RW(charge_start_threshold); +static DEVICE_ATTR_RW(charge_stop_threshold); + +static struct attribute *tpacpi_battery_attrs[] = { +	&dev_attr_charge_start_threshold.attr, +	&dev_attr_charge_stop_threshold.attr, +	NULL, +}; + +ATTRIBUTE_GROUPS(tpacpi_battery); + +/* ACPI battery hooking */ + +static int tpacpi_battery_add(struct power_supply *battery) +{ +	int batteryid = tpacpi_battery_get_id(battery->desc->name); + +	if (tpacpi_battery_probe(batteryid)) +		return -ENODEV; +	if (device_add_groups(&battery->dev, tpacpi_battery_groups)) +		return -ENODEV; +	return 0; +} + +static int tpacpi_battery_remove(struct power_supply *battery) +{ +	device_remove_groups(&battery->dev, tpacpi_battery_groups); +	return 0; +} + +static struct acpi_battery_hook battery_hook = { +	.add_battery = tpacpi_battery_add, +	.remove_battery = tpacpi_battery_remove, +	.name = "ThinkPad Battery Extension", +}; + +/* Subdriver init/exit */ + +static int __init tpacpi_battery_init(struct ibm_init_struct *ibm) +{ +	battery_hook_register(&battery_hook); +	return 0; +} + +static void tpacpi_battery_exit(void) +{ +	battery_hook_unregister(&battery_hook); +} + +static struct ibm_struct battery_driver_data = { +	.name = "battery", +	.exit = tpacpi_battery_exit, +}; +  /****************************************************************************   ****************************************************************************   * @@ -9655,6 +10038,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {  		.init = mute_led_init,  		.data = &mute_led_driver_data,  	}, +	{ +		.init = tpacpi_battery_init, +		.data = &battery_driver_data, +	},  };  static int __init set_ibm_param(const char *val, const struct kernel_param *kp) diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c index e5d81b493c45..370e9109342b 100644 --- a/drivers/power/supply/ds2780_battery.c +++ b/drivers/power/supply/ds2780_battery.c @@ -56,11 +56,6 @@ to_ds2780_device_info(struct power_supply *psy)  	return power_supply_get_drvdata(psy);  } -static inline struct power_supply *to_power_supply(struct device *dev) -{ -	return dev_get_drvdata(dev); -} -  static inline int ds2780_battery_io(struct ds2780_device_info *dev_info,  	char *buf, int addr, size_t count, int io)  { diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c index efe83ef8670c..d1b5a19aae7c 100644 --- a/drivers/power/supply/ds2781_battery.c +++ b/drivers/power/supply/ds2781_battery.c @@ -54,11 +54,6 @@ to_ds2781_device_info(struct power_supply *psy)  	return power_supply_get_drvdata(psy);  } -static inline struct power_supply *to_power_supply(struct device *dev) -{ -	return dev_get_drvdata(dev); -} -  static inline int ds2781_battery_io(struct ds2781_device_info *dev_info,  	char *buf, int addr, size_t count, int io)  { diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 82f998ab5a52..feac7b066e6c 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -668,7 +668,7 @@ EXPORT_SYMBOL_GPL(power_supply_powers);  static void power_supply_dev_release(struct device *dev)  { -	struct power_supply *psy = container_of(dev, struct power_supply, dev); +	struct power_supply *psy = to_power_supply(dev);  	dev_dbg(dev, "%s\n", __func__);  	kfree(psy);  } diff --git a/include/acpi/battery.h b/include/acpi/battery.h new file mode 100644 index 000000000000..5d8f5d910c82 --- /dev/null +++ b/include/acpi/battery.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ACPI_BATTERY_H +#define __ACPI_BATTERY_H + +#define ACPI_BATTERY_CLASS "battery" + +#define ACPI_BATTERY_NOTIFY_STATUS	0x80 +#define ACPI_BATTERY_NOTIFY_INFO	0x81 +#define ACPI_BATTERY_NOTIFY_THRESHOLD   0x82 + +struct acpi_battery_hook { +	const char *name; +	int (*add_battery)(struct power_supply *battery); +	int (*remove_battery)(struct power_supply *battery); +	struct list_head list; +}; + +void battery_hook_register(struct acpi_battery_hook *hook); +void battery_hook_unregister(struct acpi_battery_hook *hook); + +#endif diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 79e90b3d3288..f0139b460a72 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -371,6 +371,8 @@ devm_power_supply_register_no_ws(struct device *parent,  extern void power_supply_unregister(struct power_supply *psy);  extern int power_supply_powers(struct power_supply *psy, struct device *dev); +#define to_power_supply(device) container_of(device, struct power_supply, dev) +  extern void *power_supply_get_drvdata(struct power_supply *psy);  /* For APM emulation, think legacy userspace. */  extern struct class *power_supply_class; | 
