8efe444038
The CAPACITY_LEVEL stuff defines various levels of charge; however, what is the difference between them? What differentiates between HIGH and NORMAL, LOW and CRITICAL, etc? As it appears that these are fairly arbitrary, we end up making such policy decisions in the kernel (or in hardware). This is the sort of decision that should be made in userspace, not in the kernel. If the hardware does not support _CAPACITY and it cannot be easily calculated, then perhaps the driver should register a custom CAPACITY_LEVEL attribute; however, userspace should not become accustomed to looking for such a thing, and we should certainly not encourage drivers to provide CAPACITY_LEVEL stubs. The following removes support for POWER_SUPPLY_PROP_CAPACITY_LEVEL. The OLPC battery driver is the only driver making use of this, so it's removed from there as well. Signed-off-by: Andres Salomon <dilinger@debian.org> Signed-off-by: David Woodhouse <dwmw2@infradead.org>
288 lines
7.1 KiB
C
288 lines
7.1 KiB
C
/*
|
|
* Sysfs interface for the universal power supply monitor class
|
|
*
|
|
* Copyright © 2007 David Woodhouse <dwmw2@infradead.org>
|
|
* 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/ctype.h>
|
|
#include <linux/power_supply.h>
|
|
|
|
#include "power_supply.h"
|
|
|
|
/*
|
|
* This is because the name "current" breaks the device attr macro.
|
|
* The "current" word resolves to "(get_current())" so instead of
|
|
* "current" "(get_current())" appears in the sysfs.
|
|
*
|
|
* The source of this definition is the device.h which calls __ATTR
|
|
* macro in sysfs.h which calls the __stringify macro.
|
|
*
|
|
* Only modification that the name is not tried to be resolved
|
|
* (as a macro let's say).
|
|
*/
|
|
|
|
#define POWER_SUPPLY_ATTR(_name) \
|
|
{ \
|
|
.attr = { .name = #_name, .mode = 0444, .owner = THIS_MODULE }, \
|
|
.show = power_supply_show_property, \
|
|
.store = NULL, \
|
|
}
|
|
|
|
static struct device_attribute power_supply_attrs[];
|
|
|
|
static ssize_t power_supply_show_property(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf) {
|
|
static char *status_text[] = {
|
|
"Unknown", "Charging", "Discharging", "Not charging", "Full"
|
|
};
|
|
static char *health_text[] = {
|
|
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
|
|
"Unspecified failure"
|
|
};
|
|
static char *technology_text[] = {
|
|
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd"
|
|
};
|
|
ssize_t ret;
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
const ptrdiff_t off = attr - power_supply_attrs;
|
|
union power_supply_propval value;
|
|
|
|
ret = psy->get_property(psy, off, &value);
|
|
|
|
if (ret < 0) {
|
|
if (ret != -ENODEV)
|
|
dev_err(dev, "driver failed to report `%s' property\n",
|
|
attr->attr.name);
|
|
return ret;
|
|
}
|
|
|
|
if (off == POWER_SUPPLY_PROP_STATUS)
|
|
return sprintf(buf, "%s\n", status_text[value.intval]);
|
|
else if (off == POWER_SUPPLY_PROP_HEALTH)
|
|
return sprintf(buf, "%s\n", health_text[value.intval]);
|
|
else if (off == POWER_SUPPLY_PROP_TECHNOLOGY)
|
|
return sprintf(buf, "%s\n", technology_text[value.intval]);
|
|
else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
|
|
return sprintf(buf, "%s\n", value.strval);
|
|
|
|
return sprintf(buf, "%d\n", value.intval);
|
|
}
|
|
|
|
/* Must be in the same order as POWER_SUPPLY_PROP_* */
|
|
static struct device_attribute power_supply_attrs[] = {
|
|
/* Properties of type `int' */
|
|
POWER_SUPPLY_ATTR(status),
|
|
POWER_SUPPLY_ATTR(health),
|
|
POWER_SUPPLY_ATTR(present),
|
|
POWER_SUPPLY_ATTR(online),
|
|
POWER_SUPPLY_ATTR(technology),
|
|
POWER_SUPPLY_ATTR(voltage_max_design),
|
|
POWER_SUPPLY_ATTR(voltage_min_design),
|
|
POWER_SUPPLY_ATTR(voltage_now),
|
|
POWER_SUPPLY_ATTR(voltage_avg),
|
|
POWER_SUPPLY_ATTR(current_now),
|
|
POWER_SUPPLY_ATTR(current_avg),
|
|
POWER_SUPPLY_ATTR(charge_full_design),
|
|
POWER_SUPPLY_ATTR(charge_empty_design),
|
|
POWER_SUPPLY_ATTR(charge_full),
|
|
POWER_SUPPLY_ATTR(charge_empty),
|
|
POWER_SUPPLY_ATTR(charge_now),
|
|
POWER_SUPPLY_ATTR(charge_avg),
|
|
POWER_SUPPLY_ATTR(energy_full_design),
|
|
POWER_SUPPLY_ATTR(energy_empty_design),
|
|
POWER_SUPPLY_ATTR(energy_full),
|
|
POWER_SUPPLY_ATTR(energy_empty),
|
|
POWER_SUPPLY_ATTR(energy_now),
|
|
POWER_SUPPLY_ATTR(energy_avg),
|
|
POWER_SUPPLY_ATTR(capacity),
|
|
POWER_SUPPLY_ATTR(capacity_level),
|
|
POWER_SUPPLY_ATTR(temp),
|
|
POWER_SUPPLY_ATTR(temp_ambient),
|
|
POWER_SUPPLY_ATTR(time_to_empty_now),
|
|
POWER_SUPPLY_ATTR(time_to_empty_avg),
|
|
POWER_SUPPLY_ATTR(time_to_full_now),
|
|
POWER_SUPPLY_ATTR(time_to_full_avg),
|
|
/* Properties of type `const char *' */
|
|
POWER_SUPPLY_ATTR(model_name),
|
|
POWER_SUPPLY_ATTR(manufacturer),
|
|
};
|
|
|
|
static ssize_t power_supply_show_static_attrs(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf) {
|
|
static char *type_text[] = { "Battery", "UPS", "Mains", "USB" };
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
|
|
return sprintf(buf, "%s\n", type_text[psy->type]);
|
|
}
|
|
|
|
static struct device_attribute power_supply_static_attrs[] = {
|
|
__ATTR(type, 0444, power_supply_show_static_attrs, NULL),
|
|
};
|
|
|
|
int power_supply_create_attrs(struct power_supply *psy)
|
|
{
|
|
int rc = 0;
|
|
int i, j;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(power_supply_static_attrs); i++) {
|
|
rc = device_create_file(psy->dev,
|
|
&power_supply_static_attrs[i]);
|
|
if (rc)
|
|
goto statics_failed;
|
|
}
|
|
|
|
for (j = 0; j < psy->num_properties; j++) {
|
|
rc = device_create_file(psy->dev,
|
|
&power_supply_attrs[psy->properties[j]]);
|
|
if (rc)
|
|
goto dynamics_failed;
|
|
}
|
|
|
|
goto succeed;
|
|
|
|
dynamics_failed:
|
|
while (j--)
|
|
device_remove_file(psy->dev,
|
|
&power_supply_attrs[psy->properties[j]]);
|
|
statics_failed:
|
|
while (i--)
|
|
device_remove_file(psy->dev,
|
|
&power_supply_static_attrs[psy->properties[i]]);
|
|
succeed:
|
|
return rc;
|
|
}
|
|
|
|
void power_supply_remove_attrs(struct power_supply *psy)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(power_supply_static_attrs); i++)
|
|
device_remove_file(psy->dev,
|
|
&power_supply_static_attrs[i]);
|
|
|
|
for (i = 0; i < psy->num_properties; i++)
|
|
device_remove_file(psy->dev,
|
|
&power_supply_attrs[psy->properties[i]]);
|
|
}
|
|
|
|
static char *kstruprdup(const char *str, gfp_t gfp)
|
|
{
|
|
char *ret, *ustr;
|
|
|
|
ustr = ret = kmalloc(strlen(str) + 1, gfp);
|
|
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
while (*str)
|
|
*ustr++ = toupper(*str++);
|
|
|
|
*ustr = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
int ret = 0, j;
|
|
char *prop_buf;
|
|
char *attrname;
|
|
|
|
dev_dbg(dev, "uevent\n");
|
|
|
|
if (!psy) {
|
|
dev_dbg(dev, "No power supply yet\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->name);
|
|
|
|
ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
|
|
if (!prop_buf)
|
|
return -ENOMEM;
|
|
|
|
for (j = 0; j < ARRAY_SIZE(power_supply_static_attrs); j++) {
|
|
struct device_attribute *attr;
|
|
char *line;
|
|
|
|
attr = &power_supply_static_attrs[j];
|
|
|
|
ret = power_supply_show_static_attrs(dev, attr, prop_buf);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
line = strchr(prop_buf, '\n');
|
|
if (line)
|
|
*line = 0;
|
|
|
|
attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
|
|
if (!attrname) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
dev_dbg(dev, "Static prop %s=%s\n", attrname, prop_buf);
|
|
|
|
ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
|
|
kfree(attrname);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
dev_dbg(dev, "%zd dynamic props\n", psy->num_properties);
|
|
|
|
for (j = 0; j < psy->num_properties; j++) {
|
|
struct device_attribute *attr;
|
|
char *line;
|
|
|
|
attr = &power_supply_attrs[psy->properties[j]];
|
|
|
|
ret = power_supply_show_property(dev, attr, prop_buf);
|
|
if (ret == -ENODEV) {
|
|
/* When a battery is absent, we expect -ENODEV. Don't abort;
|
|
send the uevent with at least the the PRESENT=0 property */
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
line = strchr(prop_buf, '\n');
|
|
if (line)
|
|
*line = 0;
|
|
|
|
attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
|
|
if (!attrname) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);
|
|
|
|
ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
|
|
kfree(attrname);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
free_page((unsigned long)prop_buf);
|
|
|
|
return ret;
|
|
}
|