17e8351a77
The thermal code uses int, long and unsigned long for temperatures in different places. Using an unsigned type limits the thermal framework to positive temperatures without need. Also several drivers currently will report temperatures near UINT_MAX for temperatures below 0°C. This will probably immediately shut the machine down due to overtemperature if started below 0°C. 'long' is 64bit on several architectures. This is not needed since INT_MAX °mC is above the melting point of all known materials. Consistently use a plain 'int' for temperatures throughout the thermal code and the drivers. This only changes the places in the drivers where the temperature is passed around as pointer, when drivers internally use another type this is not changed. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Reviewed-by: Jean Delvare <jdelvare@suse.de> Reviewed-by: Lukasz Majewski <l.majewski@samsung.com> Reviewed-by: Darren Hart <dvhart@linux.intel.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> Reviewed-by: Peter Feuerer <peter@piie.net> Cc: Punit Agrawal <punit.agrawal@arm.com> Cc: Zhang Rui <rui.zhang@intel.com> Cc: Eduardo Valentin <edubezval@gmail.com> Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Jean Delvare <jdelvare@suse.de> Cc: Peter Feuerer <peter@piie.net> Cc: Heiko Stuebner <heiko@sntech.de> Cc: Lukasz Majewski <l.majewski@samsung.com> Cc: Stephen Warren <swarren@wwwdotorg.org> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: linux-acpi@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-omap@vger.kernel.org Cc: linux-samsung-soc@vger.kernel.org Cc: Guenter Roeck <linux@roeck-us.net> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Darren Hart <dvhart@infradead.org> Cc: lm-sensors@lm-sensors.org Signed-off-by: Zhang Rui <rui.zhang@intel.com>
980 lines
25 KiB
C
980 lines
25 KiB
C
/*
|
|
* Universal power supply monitor class
|
|
*
|
|
* Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
|
|
* Copyright © 2004 Szabolcs Gyurko
|
|
* Copyright © 2003 Ian Molton <spyro@f2s.com>
|
|
*
|
|
* Modified: 2004, Oct Szabolcs Gyurko
|
|
*
|
|
* You may use this code as per GPL version 2
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/err.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/thermal.h>
|
|
#include "power_supply.h"
|
|
|
|
/* exported for the APM Power driver, APM emulation */
|
|
struct class *power_supply_class;
|
|
EXPORT_SYMBOL_GPL(power_supply_class);
|
|
|
|
ATOMIC_NOTIFIER_HEAD(power_supply_notifier);
|
|
EXPORT_SYMBOL_GPL(power_supply_notifier);
|
|
|
|
static struct device_type power_supply_dev_type;
|
|
|
|
#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10)
|
|
|
|
static bool __power_supply_is_supplied_by(struct power_supply *supplier,
|
|
struct power_supply *supply)
|
|
{
|
|
int i;
|
|
|
|
if (!supply->supplied_from && !supplier->supplied_to)
|
|
return false;
|
|
|
|
/* Support both supplied_to and supplied_from modes */
|
|
if (supply->supplied_from) {
|
|
if (!supplier->desc->name)
|
|
return false;
|
|
for (i = 0; i < supply->num_supplies; i++)
|
|
if (!strcmp(supplier->desc->name, supply->supplied_from[i]))
|
|
return true;
|
|
} else {
|
|
if (!supply->desc->name)
|
|
return false;
|
|
for (i = 0; i < supplier->num_supplicants; i++)
|
|
if (!strcmp(supplier->supplied_to[i], supply->desc->name))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int __power_supply_changed_work(struct device *dev, void *data)
|
|
{
|
|
struct power_supply *psy = data;
|
|
struct power_supply *pst = dev_get_drvdata(dev);
|
|
|
|
if (__power_supply_is_supplied_by(psy, pst)) {
|
|
if (pst->desc->external_power_changed)
|
|
pst->desc->external_power_changed(pst);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void power_supply_changed_work(struct work_struct *work)
|
|
{
|
|
unsigned long flags;
|
|
struct power_supply *psy = container_of(work, struct power_supply,
|
|
changed_work);
|
|
|
|
dev_dbg(&psy->dev, "%s\n", __func__);
|
|
|
|
spin_lock_irqsave(&psy->changed_lock, flags);
|
|
/*
|
|
* Check 'changed' here to avoid issues due to race between
|
|
* power_supply_changed() and this routine. In worst case
|
|
* power_supply_changed() can be called again just before we take above
|
|
* lock. During the first call of this routine we will mark 'changed' as
|
|
* false and it will stay false for the next call as well.
|
|
*/
|
|
if (likely(psy->changed)) {
|
|
psy->changed = false;
|
|
spin_unlock_irqrestore(&psy->changed_lock, flags);
|
|
class_for_each_device(power_supply_class, NULL, psy,
|
|
__power_supply_changed_work);
|
|
power_supply_update_leds(psy);
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
PSY_EVENT_PROP_CHANGED, psy);
|
|
kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);
|
|
spin_lock_irqsave(&psy->changed_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Hold the wakeup_source until all events are processed.
|
|
* power_supply_changed() might have called again and have set 'changed'
|
|
* to true.
|
|
*/
|
|
if (likely(!psy->changed))
|
|
pm_relax(&psy->dev);
|
|
spin_unlock_irqrestore(&psy->changed_lock, flags);
|
|
}
|
|
|
|
void power_supply_changed(struct power_supply *psy)
|
|
{
|
|
unsigned long flags;
|
|
|
|
dev_dbg(&psy->dev, "%s\n", __func__);
|
|
|
|
spin_lock_irqsave(&psy->changed_lock, flags);
|
|
psy->changed = true;
|
|
pm_stay_awake(&psy->dev);
|
|
spin_unlock_irqrestore(&psy->changed_lock, flags);
|
|
schedule_work(&psy->changed_work);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_changed);
|
|
|
|
/*
|
|
* Notify that power supply was registered after parent finished the probing.
|
|
*
|
|
* Often power supply is registered from driver's probe function. However
|
|
* calling power_supply_changed() directly from power_supply_register()
|
|
* would lead to execution of get_property() function provided by the driver
|
|
* too early - before the probe ends.
|
|
*
|
|
* Avoid that by waiting on parent's mutex.
|
|
*/
|
|
static void power_supply_deferred_register_work(struct work_struct *work)
|
|
{
|
|
struct power_supply *psy = container_of(work, struct power_supply,
|
|
deferred_register_work.work);
|
|
|
|
if (psy->dev.parent)
|
|
mutex_lock(&psy->dev.parent->mutex);
|
|
|
|
power_supply_changed(psy);
|
|
|
|
if (psy->dev.parent)
|
|
mutex_unlock(&psy->dev.parent->mutex);
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
#include <linux/of.h>
|
|
|
|
static int __power_supply_populate_supplied_from(struct device *dev,
|
|
void *data)
|
|
{
|
|
struct power_supply *psy = data;
|
|
struct power_supply *epsy = dev_get_drvdata(dev);
|
|
struct device_node *np;
|
|
int i = 0;
|
|
|
|
do {
|
|
np = of_parse_phandle(psy->of_node, "power-supplies", i++);
|
|
if (!np)
|
|
break;
|
|
|
|
if (np == epsy->of_node) {
|
|
dev_info(&psy->dev, "%s: Found supply : %s\n",
|
|
psy->desc->name, epsy->desc->name);
|
|
psy->supplied_from[i-1] = (char *)epsy->desc->name;
|
|
psy->num_supplies++;
|
|
of_node_put(np);
|
|
break;
|
|
}
|
|
of_node_put(np);
|
|
} while (np);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int power_supply_populate_supplied_from(struct power_supply *psy)
|
|
{
|
|
int error;
|
|
|
|
error = class_for_each_device(power_supply_class, NULL, psy,
|
|
__power_supply_populate_supplied_from);
|
|
|
|
dev_dbg(&psy->dev, "%s %d\n", __func__, error);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int __power_supply_find_supply_from_node(struct device *dev,
|
|
void *data)
|
|
{
|
|
struct device_node *np = data;
|
|
struct power_supply *epsy = dev_get_drvdata(dev);
|
|
|
|
/* returning non-zero breaks out of class_for_each_device loop */
|
|
if (epsy->of_node == np)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int power_supply_find_supply_from_node(struct device_node *supply_node)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* class_for_each_device() either returns its own errors or values
|
|
* returned by __power_supply_find_supply_from_node().
|
|
*
|
|
* __power_supply_find_supply_from_node() will return 0 (no match)
|
|
* or 1 (match).
|
|
*
|
|
* We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if
|
|
* it returned 0, or error as returned by it.
|
|
*/
|
|
error = class_for_each_device(power_supply_class, NULL, supply_node,
|
|
__power_supply_find_supply_from_node);
|
|
|
|
return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER;
|
|
}
|
|
|
|
static int power_supply_check_supplies(struct power_supply *psy)
|
|
{
|
|
struct device_node *np;
|
|
int cnt = 0;
|
|
|
|
/* If there is already a list honor it */
|
|
if (psy->supplied_from && psy->num_supplies > 0)
|
|
return 0;
|
|
|
|
/* No device node found, nothing to do */
|
|
if (!psy->of_node)
|
|
return 0;
|
|
|
|
do {
|
|
int ret;
|
|
|
|
np = of_parse_phandle(psy->of_node, "power-supplies", cnt++);
|
|
if (!np)
|
|
break;
|
|
|
|
ret = power_supply_find_supply_from_node(np);
|
|
of_node_put(np);
|
|
|
|
if (ret) {
|
|
dev_dbg(&psy->dev, "Failed to find supply!\n");
|
|
return ret;
|
|
}
|
|
} while (np);
|
|
|
|
/* Missing valid "power-supplies" entries */
|
|
if (cnt == 1)
|
|
return 0;
|
|
|
|
/* All supplies found, allocate char ** array for filling */
|
|
psy->supplied_from = devm_kzalloc(&psy->dev, sizeof(psy->supplied_from),
|
|
GFP_KERNEL);
|
|
if (!psy->supplied_from) {
|
|
dev_err(&psy->dev, "Couldn't allocate memory for supply list\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*psy->supplied_from = devm_kzalloc(&psy->dev,
|
|
sizeof(char *) * (cnt - 1),
|
|
GFP_KERNEL);
|
|
if (!*psy->supplied_from) {
|
|
dev_err(&psy->dev, "Couldn't allocate memory for supply list\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return power_supply_populate_supplied_from(psy);
|
|
}
|
|
#else
|
|
static inline int power_supply_check_supplies(struct power_supply *psy)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int __power_supply_am_i_supplied(struct device *dev, void *data)
|
|
{
|
|
union power_supply_propval ret = {0,};
|
|
struct power_supply *psy = data;
|
|
struct power_supply *epsy = dev_get_drvdata(dev);
|
|
|
|
if (__power_supply_is_supplied_by(epsy, psy))
|
|
if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE,
|
|
&ret))
|
|
return ret.intval;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int power_supply_am_i_supplied(struct power_supply *psy)
|
|
{
|
|
int error;
|
|
|
|
error = class_for_each_device(power_supply_class, NULL, psy,
|
|
__power_supply_am_i_supplied);
|
|
|
|
dev_dbg(&psy->dev, "%s %d\n", __func__, error);
|
|
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_am_i_supplied);
|
|
|
|
static int __power_supply_is_system_supplied(struct device *dev, void *data)
|
|
{
|
|
union power_supply_propval ret = {0,};
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
unsigned int *count = data;
|
|
|
|
(*count)++;
|
|
if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY)
|
|
if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE,
|
|
&ret))
|
|
return ret.intval;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int power_supply_is_system_supplied(void)
|
|
{
|
|
int error;
|
|
unsigned int count = 0;
|
|
|
|
error = class_for_each_device(power_supply_class, NULL, &count,
|
|
__power_supply_is_system_supplied);
|
|
|
|
/*
|
|
* If no power class device was found at all, most probably we are
|
|
* running on a desktop system, so assume we are on mains power.
|
|
*/
|
|
if (count == 0)
|
|
return 1;
|
|
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_is_system_supplied);
|
|
|
|
int power_supply_set_battery_charged(struct power_supply *psy)
|
|
{
|
|
if (atomic_read(&psy->use_cnt) >= 0 &&
|
|
psy->desc->type == POWER_SUPPLY_TYPE_BATTERY &&
|
|
psy->desc->set_charged) {
|
|
psy->desc->set_charged(psy);
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);
|
|
|
|
static int power_supply_match_device_by_name(struct device *dev, const void *data)
|
|
{
|
|
const char *name = data;
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
|
|
return strcmp(psy->desc->name, name) == 0;
|
|
}
|
|
|
|
/**
|
|
* power_supply_get_by_name() - Search for a power supply and returns its ref
|
|
* @name: Power supply name to fetch
|
|
*
|
|
* If power supply was found, it increases reference count for the
|
|
* internal power supply's device. The user should power_supply_put()
|
|
* after usage.
|
|
*
|
|
* Return: On success returns a reference to a power supply with
|
|
* matching name equals to @name, a NULL otherwise.
|
|
*/
|
|
struct power_supply *power_supply_get_by_name(const char *name)
|
|
{
|
|
struct power_supply *psy = NULL;
|
|
struct device *dev = class_find_device(power_supply_class, NULL, name,
|
|
power_supply_match_device_by_name);
|
|
|
|
if (dev) {
|
|
psy = dev_get_drvdata(dev);
|
|
atomic_inc(&psy->use_cnt);
|
|
}
|
|
|
|
return psy;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_get_by_name);
|
|
|
|
/**
|
|
* power_supply_put() - Drop reference obtained with power_supply_get_by_name
|
|
* @psy: Reference to put
|
|
*
|
|
* The reference to power supply should be put before unregistering
|
|
* the power supply.
|
|
*/
|
|
void power_supply_put(struct power_supply *psy)
|
|
{
|
|
might_sleep();
|
|
|
|
atomic_dec(&psy->use_cnt);
|
|
put_device(&psy->dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_put);
|
|
|
|
#ifdef CONFIG_OF
|
|
static int power_supply_match_device_node(struct device *dev, const void *data)
|
|
{
|
|
return dev->parent && dev->parent->of_node == data;
|
|
}
|
|
|
|
/**
|
|
* power_supply_get_by_phandle() - Search for a power supply and returns its ref
|
|
* @np: Pointer to device node holding phandle property
|
|
* @phandle_name: Name of property holding a power supply name
|
|
*
|
|
* If power supply was found, it increases reference count for the
|
|
* internal power supply's device. The user should power_supply_put()
|
|
* after usage.
|
|
*
|
|
* Return: On success returns a reference to a power supply with
|
|
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
|
|
*/
|
|
struct power_supply *power_supply_get_by_phandle(struct device_node *np,
|
|
const char *property)
|
|
{
|
|
struct device_node *power_supply_np;
|
|
struct power_supply *psy = NULL;
|
|
struct device *dev;
|
|
|
|
power_supply_np = of_parse_phandle(np, property, 0);
|
|
if (!power_supply_np)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
dev = class_find_device(power_supply_class, NULL, power_supply_np,
|
|
power_supply_match_device_node);
|
|
|
|
of_node_put(power_supply_np);
|
|
|
|
if (dev) {
|
|
psy = dev_get_drvdata(dev);
|
|
atomic_inc(&psy->use_cnt);
|
|
}
|
|
|
|
return psy;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
|
|
|
|
static void devm_power_supply_put(struct device *dev, void *res)
|
|
{
|
|
struct power_supply **psy = res;
|
|
|
|
power_supply_put(*psy);
|
|
}
|
|
|
|
/**
|
|
* devm_power_supply_get_by_phandle() - Resource managed version of
|
|
* power_supply_get_by_phandle()
|
|
* @dev: Pointer to device holding phandle property
|
|
* @phandle_name: Name of property holding a power supply phandle
|
|
*
|
|
* Return: On success returns a reference to a power supply with
|
|
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
|
|
*/
|
|
struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
|
|
const char *property)
|
|
{
|
|
struct power_supply **ptr, *psy;
|
|
|
|
if (!dev->of_node)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
|
|
if (!ptr)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
psy = power_supply_get_by_phandle(dev->of_node, property);
|
|
if (IS_ERR_OR_NULL(psy)) {
|
|
devres_free(ptr);
|
|
} else {
|
|
*ptr = psy;
|
|
devres_add(dev, ptr);
|
|
}
|
|
return psy;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
|
|
#endif /* CONFIG_OF */
|
|
|
|
int power_supply_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
if (atomic_read(&psy->use_cnt) <= 0)
|
|
return -ENODEV;
|
|
|
|
return psy->desc->get_property(psy, psp, val);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_get_property);
|
|
|
|
int power_supply_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property)
|
|
return -ENODEV;
|
|
|
|
return psy->desc->set_property(psy, psp, val);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_set_property);
|
|
|
|
int power_supply_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
if (atomic_read(&psy->use_cnt) <= 0 ||
|
|
!psy->desc->property_is_writeable)
|
|
return -ENODEV;
|
|
|
|
return psy->desc->property_is_writeable(psy, psp);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_property_is_writeable);
|
|
|
|
void power_supply_external_power_changed(struct power_supply *psy)
|
|
{
|
|
if (atomic_read(&psy->use_cnt) <= 0 ||
|
|
!psy->desc->external_power_changed)
|
|
return;
|
|
|
|
psy->desc->external_power_changed(psy);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_external_power_changed);
|
|
|
|
int power_supply_powers(struct power_supply *psy, struct device *dev)
|
|
{
|
|
return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers");
|
|
}
|
|
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);
|
|
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
|
|
kfree(psy);
|
|
}
|
|
|
|
int power_supply_reg_notifier(struct notifier_block *nb)
|
|
{
|
|
return atomic_notifier_chain_register(&power_supply_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_reg_notifier);
|
|
|
|
void power_supply_unreg_notifier(struct notifier_block *nb)
|
|
{
|
|
atomic_notifier_chain_unregister(&power_supply_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_unreg_notifier);
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static int power_supply_read_temp(struct thermal_zone_device *tzd,
|
|
int *temp)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
WARN_ON(tzd == NULL);
|
|
psy = tzd->devdata;
|
|
ret = psy->desc->get_property(psy, POWER_SUPPLY_PROP_TEMP, &val);
|
|
|
|
/* Convert tenths of degree Celsius to milli degree Celsius. */
|
|
if (!ret)
|
|
*temp = val.intval * 100;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops psy_tzd_ops = {
|
|
.get_temp = power_supply_read_temp,
|
|
};
|
|
|
|
static int psy_register_thermal(struct power_supply *psy)
|
|
{
|
|
int i;
|
|
|
|
if (psy->desc->no_thermal)
|
|
return 0;
|
|
|
|
/* Register battery zone device psy reports temperature */
|
|
for (i = 0; i < psy->desc->num_properties; i++) {
|
|
if (psy->desc->properties[i] == POWER_SUPPLY_PROP_TEMP) {
|
|
psy->tzd = thermal_zone_device_register(psy->desc->name,
|
|
0, 0, psy, &psy_tzd_ops, NULL, 0, 0);
|
|
return PTR_ERR_OR_ZERO(psy->tzd);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void psy_unregister_thermal(struct power_supply *psy)
|
|
{
|
|
if (IS_ERR_OR_NULL(psy->tzd))
|
|
return;
|
|
thermal_zone_device_unregister(psy->tzd);
|
|
}
|
|
|
|
/* thermal cooling device callbacks */
|
|
static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
|
|
unsigned long *state)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
psy = tcd->devdata;
|
|
ret = psy->desc->get_property(psy,
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val);
|
|
if (!ret)
|
|
*state = val.intval;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd,
|
|
unsigned long *state)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
psy = tcd->devdata;
|
|
ret = psy->desc->get_property(psy,
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
|
|
if (!ret)
|
|
*state = val.intval;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
|
|
unsigned long state)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
psy = tcd->devdata;
|
|
val.intval = state;
|
|
ret = psy->desc->set_property(psy,
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_cooling_device_ops psy_tcd_ops = {
|
|
.get_max_state = ps_get_max_charge_cntl_limit,
|
|
.get_cur_state = ps_get_cur_chrage_cntl_limit,
|
|
.set_cur_state = ps_set_cur_charge_cntl_limit,
|
|
};
|
|
|
|
static int psy_register_cooler(struct power_supply *psy)
|
|
{
|
|
int i;
|
|
|
|
/* Register for cooling device if psy can control charging */
|
|
for (i = 0; i < psy->desc->num_properties; i++) {
|
|
if (psy->desc->properties[i] ==
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) {
|
|
psy->tcd = thermal_cooling_device_register(
|
|
(char *)psy->desc->name,
|
|
psy, &psy_tcd_ops);
|
|
return PTR_ERR_OR_ZERO(psy->tcd);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void psy_unregister_cooler(struct power_supply *psy)
|
|
{
|
|
if (IS_ERR_OR_NULL(psy->tcd))
|
|
return;
|
|
thermal_cooling_device_unregister(psy->tcd);
|
|
}
|
|
#else
|
|
static int psy_register_thermal(struct power_supply *psy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void psy_unregister_thermal(struct power_supply *psy)
|
|
{
|
|
}
|
|
|
|
static int psy_register_cooler(struct power_supply *psy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void psy_unregister_cooler(struct power_supply *psy)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static struct power_supply *__must_check
|
|
__power_supply_register(struct device *parent,
|
|
const struct power_supply_desc *desc,
|
|
const struct power_supply_config *cfg,
|
|
bool ws)
|
|
{
|
|
struct device *dev;
|
|
struct power_supply *psy;
|
|
int rc;
|
|
|
|
if (!parent)
|
|
pr_warn("%s: Expected proper parent device for '%s'\n",
|
|
__func__, desc->name);
|
|
|
|
psy = kzalloc(sizeof(*psy), GFP_KERNEL);
|
|
if (!psy)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
dev = &psy->dev;
|
|
|
|
device_initialize(dev);
|
|
|
|
dev->class = power_supply_class;
|
|
dev->type = &power_supply_dev_type;
|
|
dev->parent = parent;
|
|
dev->release = power_supply_dev_release;
|
|
dev_set_drvdata(dev, psy);
|
|
psy->desc = desc;
|
|
if (cfg) {
|
|
psy->drv_data = cfg->drv_data;
|
|
psy->of_node = cfg->of_node;
|
|
psy->supplied_to = cfg->supplied_to;
|
|
psy->num_supplicants = cfg->num_supplicants;
|
|
}
|
|
|
|
rc = dev_set_name(dev, "%s", desc->name);
|
|
if (rc)
|
|
goto dev_set_name_failed;
|
|
|
|
INIT_WORK(&psy->changed_work, power_supply_changed_work);
|
|
INIT_DELAYED_WORK(&psy->deferred_register_work,
|
|
power_supply_deferred_register_work);
|
|
|
|
rc = power_supply_check_supplies(psy);
|
|
if (rc) {
|
|
dev_info(dev, "Not all required supplies found, defer probe\n");
|
|
goto check_supplies_failed;
|
|
}
|
|
|
|
spin_lock_init(&psy->changed_lock);
|
|
rc = device_init_wakeup(dev, ws);
|
|
if (rc)
|
|
goto wakeup_init_failed;
|
|
|
|
rc = device_add(dev);
|
|
if (rc)
|
|
goto device_add_failed;
|
|
|
|
rc = psy_register_thermal(psy);
|
|
if (rc)
|
|
goto register_thermal_failed;
|
|
|
|
rc = psy_register_cooler(psy);
|
|
if (rc)
|
|
goto register_cooler_failed;
|
|
|
|
rc = power_supply_create_triggers(psy);
|
|
if (rc)
|
|
goto create_triggers_failed;
|
|
|
|
/*
|
|
* Update use_cnt after any uevents (most notably from device_add()).
|
|
* We are here still during driver's probe but
|
|
* the power_supply_uevent() calls back driver's get_property
|
|
* method so:
|
|
* 1. Driver did not assigned the returned struct power_supply,
|
|
* 2. Driver could not finish initialization (anything in its probe
|
|
* after calling power_supply_register()).
|
|
*/
|
|
atomic_inc(&psy->use_cnt);
|
|
|
|
queue_delayed_work(system_power_efficient_wq,
|
|
&psy->deferred_register_work,
|
|
POWER_SUPPLY_DEFERRED_REGISTER_TIME);
|
|
|
|
return psy;
|
|
|
|
create_triggers_failed:
|
|
psy_unregister_cooler(psy);
|
|
register_cooler_failed:
|
|
psy_unregister_thermal(psy);
|
|
register_thermal_failed:
|
|
device_del(dev);
|
|
device_add_failed:
|
|
wakeup_init_failed:
|
|
check_supplies_failed:
|
|
dev_set_name_failed:
|
|
put_device(dev);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
/**
|
|
* power_supply_register() - Register new power supply
|
|
* @parent: Device to be a parent of power supply's device, usually
|
|
* the device which probe function calls this
|
|
* @desc: Description of power supply, must be valid through whole
|
|
* lifetime of this power supply
|
|
* @cfg: Run-time specific configuration accessed during registering,
|
|
* may be NULL
|
|
*
|
|
* Return: A pointer to newly allocated power_supply on success
|
|
* or ERR_PTR otherwise.
|
|
* Use power_supply_unregister() on returned power_supply pointer to release
|
|
* resources.
|
|
*/
|
|
struct power_supply *__must_check power_supply_register(struct device *parent,
|
|
const struct power_supply_desc *desc,
|
|
const struct power_supply_config *cfg)
|
|
{
|
|
return __power_supply_register(parent, desc, cfg, true);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_register);
|
|
|
|
/**
|
|
* power_supply_register_no_ws() - Register new non-waking-source power supply
|
|
* @parent: Device to be a parent of power supply's device, usually
|
|
* the device which probe function calls this
|
|
* @desc: Description of power supply, must be valid through whole
|
|
* lifetime of this power supply
|
|
* @cfg: Run-time specific configuration accessed during registering,
|
|
* may be NULL
|
|
*
|
|
* Return: A pointer to newly allocated power_supply on success
|
|
* or ERR_PTR otherwise.
|
|
* Use power_supply_unregister() on returned power_supply pointer to release
|
|
* resources.
|
|
*/
|
|
struct power_supply *__must_check
|
|
power_supply_register_no_ws(struct device *parent,
|
|
const struct power_supply_desc *desc,
|
|
const struct power_supply_config *cfg)
|
|
{
|
|
return __power_supply_register(parent, desc, cfg, false);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_register_no_ws);
|
|
|
|
static void devm_power_supply_release(struct device *dev, void *res)
|
|
{
|
|
struct power_supply **psy = res;
|
|
|
|
power_supply_unregister(*psy);
|
|
}
|
|
|
|
/**
|
|
* devm_power_supply_register() - Register managed power supply
|
|
* @parent: Device to be a parent of power supply's device, usually
|
|
* the device which probe function calls this
|
|
* @desc: Description of power supply, must be valid through whole
|
|
* lifetime of this power supply
|
|
* @cfg: Run-time specific configuration accessed during registering,
|
|
* may be NULL
|
|
*
|
|
* Return: A pointer to newly allocated power_supply on success
|
|
* or ERR_PTR otherwise.
|
|
* The returned power_supply pointer will be automatically unregistered
|
|
* on driver detach.
|
|
*/
|
|
struct power_supply *__must_check
|
|
devm_power_supply_register(struct device *parent,
|
|
const struct power_supply_desc *desc,
|
|
const struct power_supply_config *cfg)
|
|
{
|
|
struct power_supply **ptr, *psy;
|
|
|
|
ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);
|
|
|
|
if (!ptr)
|
|
return ERR_PTR(-ENOMEM);
|
|
psy = __power_supply_register(parent, desc, cfg, true);
|
|
if (IS_ERR(psy)) {
|
|
devres_free(ptr);
|
|
} else {
|
|
*ptr = psy;
|
|
devres_add(parent, ptr);
|
|
}
|
|
return psy;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_power_supply_register);
|
|
|
|
/**
|
|
* devm_power_supply_register_no_ws() - Register managed non-waking-source power supply
|
|
* @parent: Device to be a parent of power supply's device, usually
|
|
* the device which probe function calls this
|
|
* @desc: Description of power supply, must be valid through whole
|
|
* lifetime of this power supply
|
|
* @cfg: Run-time specific configuration accessed during registering,
|
|
* may be NULL
|
|
*
|
|
* Return: A pointer to newly allocated power_supply on success
|
|
* or ERR_PTR otherwise.
|
|
* The returned power_supply pointer will be automatically unregistered
|
|
* on driver detach.
|
|
*/
|
|
struct power_supply *__must_check
|
|
devm_power_supply_register_no_ws(struct device *parent,
|
|
const struct power_supply_desc *desc,
|
|
const struct power_supply_config *cfg)
|
|
{
|
|
struct power_supply **ptr, *psy;
|
|
|
|
ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);
|
|
|
|
if (!ptr)
|
|
return ERR_PTR(-ENOMEM);
|
|
psy = __power_supply_register(parent, desc, cfg, false);
|
|
if (IS_ERR(psy)) {
|
|
devres_free(ptr);
|
|
} else {
|
|
*ptr = psy;
|
|
devres_add(parent, ptr);
|
|
}
|
|
return psy;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws);
|
|
|
|
/**
|
|
* power_supply_unregister() - Remove this power supply from system
|
|
* @psy: Pointer to power supply to unregister
|
|
*
|
|
* Remove this power supply from the system. The resources of power supply
|
|
* will be freed here or on last power_supply_put() call.
|
|
*/
|
|
void power_supply_unregister(struct power_supply *psy)
|
|
{
|
|
WARN_ON(atomic_dec_return(&psy->use_cnt));
|
|
cancel_work_sync(&psy->changed_work);
|
|
cancel_delayed_work_sync(&psy->deferred_register_work);
|
|
sysfs_remove_link(&psy->dev.kobj, "powers");
|
|
power_supply_remove_triggers(psy);
|
|
psy_unregister_cooler(psy);
|
|
psy_unregister_thermal(psy);
|
|
device_init_wakeup(&psy->dev, false);
|
|
device_unregister(&psy->dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_unregister);
|
|
|
|
void *power_supply_get_drvdata(struct power_supply *psy)
|
|
{
|
|
return psy->drv_data;
|
|
}
|
|
EXPORT_SYMBOL_GPL(power_supply_get_drvdata);
|
|
|
|
static int __init power_supply_class_init(void)
|
|
{
|
|
power_supply_class = class_create(THIS_MODULE, "power_supply");
|
|
|
|
if (IS_ERR(power_supply_class))
|
|
return PTR_ERR(power_supply_class);
|
|
|
|
power_supply_class->dev_uevent = power_supply_uevent;
|
|
power_supply_init_attrs(&power_supply_dev_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit power_supply_class_exit(void)
|
|
{
|
|
class_destroy(power_supply_class);
|
|
}
|
|
|
|
subsys_initcall(power_supply_class_init);
|
|
module_exit(power_supply_class_exit);
|
|
|
|
MODULE_DESCRIPTION("Universal power supply monitor class");
|
|
MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, "
|
|
"Szabolcs Gyurko, "
|
|
"Anton Vorontsov <cbou@mail.ru>");
|
|
MODULE_LICENSE("GPL");
|