Commit 5f60cfd9 authored by Linus Torvalds's avatar Linus Torvalds

Merge git://git.infradead.org/~dwmw2/battery-2.6

* git://git.infradead.org/~dwmw2/battery-2.6:
  [BATTERY] ds2760 W1 slave
  [BATTERY] One Laptop Per Child power/battery driver
  [BATTERY] Apple PMU driver
  [BATTERY] 1-Wire ds2760 chip battery driver
  [BATTERY] APM emulation driver for class batteries
  [BATTERY] pda_power platform driver
  [BATTERY] Universal power supply class (was: battery class)
parents 9f9d7632 d7ce6d1d
Linux power supply class
========================
Synopsis
~~~~~~~~
Power supply class used to represent battery, UPS, AC or DC power supply
properties to user-space.
It defines core set of attributes, which should be applicable to (almost)
every power supply out there. Attributes are available via sysfs and uevent
interfaces.
Each attribute has well defined meaning, up to unit of measure used. While
the attributes provided are believed to be universally applicable to any
power supply, specific monitoring hardware may not be able to provide them
all, so any of them may be skipped.
Power supply class is extensible, and allows to define drivers own attributes.
The core attribute set is subject to the standard Linux evolution (i.e.
if it will be found that some attribute is applicable to many power supply
types or their drivers, it can be added to the core set).
It also integrates with LED framework, for the purpose of providing
typically expected feedback of battery charging/fully charged status and
AC/USB power supply online status. (Note that specific details of the
indication (including whether to use it at all) are fully controllable by
user and/or specific machine defaults, per design principles of LED
framework).
Attributes/properties
~~~~~~~~~~~~~~~~~~~~~
Power supply class has predefined set of attributes, this eliminates code
duplication across drivers. Power supply class insist on reusing its
predefined attributes *and* their units.
So, userspace gets predictable set of attributes and their units for any
kind of power supply, and can process/present them to a user in consistent
manner. Results for different power supplies and machines are also directly
comparable.
See drivers/power/ds2760_battery.c and drivers/power/pda_power.c for the
example how to declare and handle attributes.
Units
~~~~~
Quoting include/linux/power_supply.h:
All voltages, currents, charges, energies, time and temperatures in µV,
µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise
stated. It's driver's job to convert its raw values to units in which
this class operates.
Attributes/properties detailed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ ~ ~ ~ ~ ~ ~ Charge/Energy/Capacity - how to not confuse ~ ~ ~ ~ ~ ~ ~
~ ~
~ Because both "charge" (µAh) and "energy" (µWh) represents "capacity" ~
~ of battery, this class distinguish these terms. Don't mix them! ~
~ ~
~ CHARGE_* attributes represents capacity in µAh only. ~
~ ENERGY_* attributes represents capacity in µWh only. ~
~ CAPACITY attribute represents capacity in *percents*, from 0 to 100. ~
~ ~
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Postfixes:
_AVG - *hardware* averaged value, use it if your hardware is really able to
report averaged values.
_NOW - momentary/instantaneous values.
STATUS - this attribute represents operating status (charging, full,
discharging (i.e. powering a load), etc.). This corresponds to
BATTERY_STATUS_* values, as defined in battery.h.
HEALTH - represents health of the battery, values corresponds to
POWER_SUPPLY_HEALTH_*, defined in battery.h.
VOLTAGE_MAX_DESIGN, VOLTAGE_MIN_DESIGN - design values for maximal and
minimal power supply voltages. Maximal/minimal means values of voltages
when battery considered "full"/"empty" at normal conditions. Yes, there is
no direct relation between voltage and battery capacity, but some dumb
batteries use voltage for very approximated calculation of capacity.
Battery driver also can use this attribute just to inform userspace
about maximal and minimal voltage thresholds of a given battery.
CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN - design charge values, when
battery considered full/empty.
ENERGY_FULL_DESIGN, ENERGY_EMPTY_DESIGN - same as above but for energy.
CHARGE_FULL, CHARGE_EMPTY - These attributes means "last remembered value
of charge when battery became full/empty". It also could mean "value of
charge when battery considered full/empty at given conditions (temperature,
age)". I.e. these attributes represents real thresholds, not design values.
ENERGY_FULL, ENERGY_EMPTY - same as above but for energy.
CAPACITY - capacity in percents.
CAPACITY_LEVEL - capacity level. This corresponds to
POWER_SUPPLY_CAPACITY_LEVEL_*.
TEMP - temperature of the power supply.
TEMP_AMBIENT - ambient temperature.
TIME_TO_EMPTY - seconds left for battery to be considered empty (i.e.
while battery powers a load)
TIME_TO_FULL - seconds left for battery to be considered full (i.e.
while battery is charging)
Battery <-> external power supply interaction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Often power supplies are acting as supplies and supplicants at the same
time. Batteries are good example. So, batteries usually care if they're
externally powered or not.
For that case, power supply class implements notification mechanism for
batteries.
External power supply (AC) lists supplicants (batteries) names in
"supplied_to" struct member, and each power_supply_changed() call
issued by external power supply will notify supplicants via
external_power_changed callback.
QA
~~
Q: Where is POWER_SUPPLY_PROP_XYZ attribute?
A: If you cannot find attribute suitable for your driver needs, feel free
to add it and send patch along with your driver.
The attributes available currently are the ones currently provided by the
drivers written.
Good candidates to add in future: model/part#, cycle_time, manufacturer,
etc.
Q: I have some very specific attribute (e.g. battery color), should I add
this attribute to standard ones?
A: Most likely, no. Such attribute can be placed in the driver itself, if
it is useful. Of course, if the attribute in question applicable to
large set of batteries, provided by many drivers, and/or comes from
some general battery specification/standard, it may be a candidate to
be added to the core attribute set.
Q: Suppose, my battery monitoring chip/firmware does not provides capacity
in percents, but provides charge_{now,full,empty}. Should I calculate
percentage capacity manually, inside the driver, and register CAPACITY
attribute? The same question about time_to_empty/time_to_full.
A: Most likely, no. This class is designed to export properties which are
directly measurable by the specific hardware available.
Inferring not available properties using some heuristics or mathematical
model is not subject of work for a battery driver. Such functionality
should be factored out, and in fact, apm_power, the driver to serve
legacy APM API on top of power supply class, uses a simple heuristic of
approximating remaining battery capacity based on its charge, current,
voltage and so on. But full-fledged battery model is likely not subject
for kernel at all, as it would require floating point calculation to deal
with things like differential equations and Kalman filters. This is
better be handled by batteryd/libbattery, yet to be written.
...@@ -52,6 +52,8 @@ source "drivers/spi/Kconfig" ...@@ -52,6 +52,8 @@ source "drivers/spi/Kconfig"
source "drivers/w1/Kconfig" source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
source "drivers/hwmon/Kconfig" source "drivers/hwmon/Kconfig"
source "drivers/mfd/Kconfig" source "drivers/mfd/Kconfig"
......
...@@ -61,6 +61,7 @@ obj-$(CONFIG_I2O) += message/ ...@@ -61,6 +61,7 @@ obj-$(CONFIG_I2O) += message/
obj-$(CONFIG_RTC_LIB) += rtc/ obj-$(CONFIG_RTC_LIB) += rtc/
obj-y += i2c/ obj-y += i2c/
obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_W1) += w1/
obj-$(CONFIG_POWER_SUPPLY) += power/
obj-$(CONFIG_HWMON) += hwmon/ obj-$(CONFIG_HWMON) += hwmon/
obj-$(CONFIG_PHONE) += telephony/ obj-$(CONFIG_PHONE) += telephony/
obj-$(CONFIG_MD) += md/ obj-$(CONFIG_MD) += md/
......
menuconfig POWER_SUPPLY
tristate "Power supply class support"
help
Say Y here to enable power supply class support. This allows
power supply (batteries, AC, USB) monitoring by userspace
via sysfs and uevent (if available) and/or APM kernel interface
(if selected below).
if POWER_SUPPLY
config POWER_SUPPLY_DEBUG
bool "Power supply debug"
help
Say Y here to enable debugging messages for power supply class
and drivers.
config PDA_POWER
tristate "Generic PDA/phone power driver"
help
Say Y here to enable generic power driver for PDAs and phones with
one or two external power supplies (AC/USB) connected to main and
backup batteries, and optional builtin charger.
config APM_POWER
tristate "APM emulation for class batteries"
depends on APM_EMULATION
help
Say Y here to enable support APM status emulation using
battery class devices.
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
select W1
select W1_SLAVE_DS2760
help
Say Y here to enable support for batteries with ds2760 chip.
config BATTERY_PMU
tristate "Apple PMU battery"
depends on ADB_PMU
help
Say Y here to expose battery information on Apple machines
through the generic battery class.
config BATTERY_OLPC
tristate "One Laptop Per Child battery"
depends on X86_32 && OLPC
help
Say Y to enable support for the battery on the OLPC laptop.
endif # POWER_SUPPLY
power_supply-objs := power_supply_core.o
ifeq ($(CONFIG_SYSFS),y)
power_supply-objs += power_supply_sysfs.o
endif
ifeq ($(CONFIG_LEDS_TRIGGERS),y)
power_supply-objs += power_supply_leds.o
endif
ifeq ($(CONFIG_POWER_SUPPLY_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif
obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_PDA_POWER) += pda_power.o
obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
/*
* Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
* Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru>
*
* Author: Eugeny Boger <eugenyboger@dgap.mipt.ru>
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*/
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/apm-emulation.h>
#define PSY_PROP(psy, prop, val) psy->get_property(psy, \
POWER_SUPPLY_PROP_##prop, val)
#define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \
prop, val)
#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val)
static struct power_supply *main_battery;
static void find_main_battery(void)
{
struct device *dev;
struct power_supply *bat, *batm;
union power_supply_propval full;
int max_charge = 0;
main_battery = NULL;
batm = NULL;
list_for_each_entry(dev, &power_supply_class->devices, node) {
bat = dev_get_drvdata(dev);
/* If none of battery devices cantains 'use_for_apm' flag,
choice one with maximum design charge */
if (!PSY_PROP(bat, CHARGE_FULL_DESIGN, &full)) {
if (full.intval > max_charge) {
batm = bat;
max_charge = full.intval;
}
}
if (bat->use_for_apm)
main_battery = bat;
}
if (!main_battery)
main_battery = batm;
return;
}
static int calculate_time(int status)
{
union power_supply_propval charge_full, charge_empty;
union power_supply_propval charge, I;
if (MPSY_PROP(CHARGE_FULL, &charge_full)) {
/* if battery can't report this property, use design value */
if (MPSY_PROP(CHARGE_FULL_DESIGN, &charge_full))
return -1;
}
if (MPSY_PROP(CHARGE_EMPTY, &charge_empty)) {
/* if battery can't report this property, use design value */
if (MPSY_PROP(CHARGE_EMPTY_DESIGN, &charge_empty))
charge_empty.intval = 0;
}
if (MPSY_PROP(CHARGE_AVG, &charge)) {
/* if battery can't report average value, use momentary */
if (MPSY_PROP(CHARGE_NOW, &charge))
return -1;
}
if (MPSY_PROP(CURRENT_AVG, &I)) {
/* if battery can't report average value, use momentary */
if (MPSY_PROP(CURRENT_NOW, &I))
return -1;
}
if (status == POWER_SUPPLY_STATUS_CHARGING)
return ((charge.intval - charge_full.intval) * 60L) /
I.intval;
else
return -((charge.intval - charge_empty.intval) * 60L) /
I.intval;
}
static int calculate_capacity(int using_charge)
{
enum power_supply_property full_prop, empty_prop;
enum power_supply_property full_design_prop, empty_design_prop;
enum power_supply_property now_prop, avg_prop;
union power_supply_propval empty, full, cur;
int ret;
if (using_charge) {
full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
} else {
full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
}
if (_MPSY_PROP(full_prop, &full)) {
/* if battery can't report this property, use design value */
if (_MPSY_PROP(full_design_prop, &full))
return -1;
}
if (_MPSY_PROP(avg_prop, &cur)) {
/* if battery can't report average value, use momentary */
if (_MPSY_PROP(now_prop, &cur))
return -1;
}
if (_MPSY_PROP(empty_prop, &empty)) {
/* if battery can't report this property, use design value */
if (_MPSY_PROP(empty_design_prop, &empty))
empty.intval = 0;
}
if (full.intval - empty.intval)
ret = ((cur.intval - empty.intval) * 100L) /
(full.intval - empty.intval);
else
return -1;
if (ret > 100)
return 100;
else if (ret < 0)
return 0;
return ret;
}
static void apm_battery_apm_get_power_status(struct apm_power_info *info)
{
union power_supply_propval status;
union power_supply_propval capacity, time_to_full, time_to_empty;
down(&power_supply_class->sem);
find_main_battery();
if (!main_battery) {
up(&power_supply_class->sem);
return;
}
/* status */
if (MPSY_PROP(STATUS, &status))
status.intval = POWER_SUPPLY_STATUS_UNKNOWN;
/* ac line status */
if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) ||
(status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) ||
(status.intval == POWER_SUPPLY_STATUS_FULL))
info->ac_line_status = APM_AC_ONLINE;
else
info->ac_line_status = APM_AC_OFFLINE;
/* battery life (i.e. capacity, in percents) */
if (MPSY_PROP(CAPACITY, &capacity) == 0) {
info->battery_life = capacity.intval;
} else {
/* try calculate using energy */
info->battery_life = calculate_capacity(0);
/* if failed try calculate using charge instead */
if (info->battery_life == -1)
info->battery_life = calculate_capacity(1);
}
/* charging status */
if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
info->battery_status = APM_BATTERY_STATUS_CHARGING;
} else {
if (info->battery_life > 50)
info->battery_status = APM_BATTERY_STATUS_HIGH;
else if (info->battery_life > 5)
info->battery_status = APM_BATTERY_STATUS_LOW;
else
info->battery_status = APM_BATTERY_STATUS_CRITICAL;
}
info->battery_flag = info->battery_status;
/* time */
info->units = APM_UNITS_MINS;
if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
if (MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full)) {
if (MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full))
info->time = calculate_time(status.intval);
else
info->time = time_to_full.intval / 60;
}
} else {
if (MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty)) {
if (MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty))
info->time = calculate_time(status.intval);
else
info->time = time_to_empty.intval / 60;
}
}
up(&power_supply_class->sem);
return;
}
static int __init apm_battery_init(void)
{
printk(KERN_INFO "APM Battery Driver\n");
apm_get_power_status = apm_battery_apm_get_power_status;
return 0;
}
static void __exit apm_battery_exit(void)
{
apm_get_power_status = NULL;
return;
}
module_init(apm_battery_init);
module_exit(apm_battery_exit);
MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
MODULE_LICENSE("GPL");
/*
* Driver for batteries with DS2760 chips inside.
*
* Copyright © 2007 Anton Vorontsov
* 2004-2007 Matt Reimer
* 2004 Szabolcs Gyurko
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
* Author: Anton Vorontsov <cbou@mail.ru>
* February 2007
*
* Matt Reimer <mreimer@vpop.net>
* April 2004, 2005, 2007
*
* Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
* September 2004
*/
#include <linux/module.h>
#include <linux/param.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include "../w1/w1.h"
#include "../w1/slaves/w1_ds2760.h"
struct ds2760_device_info {
struct device *dev;
/* DS2760 data, valid after calling ds2760_battery_read_status() */
unsigned long update_time; /* jiffies when data read */
char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */
int voltage_raw; /* units of 4.88 mV */
int voltage_uV; /* units of µV */
int current_raw; /* units of 0.625 mA */
int current_uA; /* units of µA */
int accum_current_raw; /* units of 0.25 mAh */
int accum_current_uAh; /* units of µAh */
int temp_raw; /* units of 0.125 °C */
int temp_C; /* units of 0.1 °C */
int rated_capacity; /* units of µAh */
int rem_capacity; /* percentage */
int full_active_uAh; /* units of µAh */
int empty_uAh; /* units of µAh */
int life_sec; /* units of seconds */
int charge_status; /* POWER_SUPPLY_STATUS_* */
int full_counter;
struct power_supply bat;
struct device *w1_dev;
struct workqueue_struct *monitor_wqueue;
struct delayed_work monitor_work;
};
static unsigned int cache_time = 1000;
module_param(cache_time, uint, 0644);
MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
/* Some batteries have their rated capacity stored a N * 10 mAh, while
* others use an index into this table. */
static int rated_capacities[] = {
0,
920, /* Samsung */
920, /* BYD */
920, /* Lishen */
920, /* NEC */
1440, /* Samsung */
1440, /* BYD */
1440, /* Lishen */
1440, /* NEC */
2880, /* Samsung */
2880, /* BYD */
2880, /* Lishen */
2880 /* NEC */
};
/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C
* temp is in Celsius */
static int battery_interpolate(int array[], int temp)
{
int index, dt;
if (temp <= 0)
return array[0];
if (temp >= 40)
return array[4];
index = temp / 10;
dt = temp % 10;
return array[index] + (((array[index + 1] - array[index]) * dt) / 10);
}
static int ds2760_battery_read_status(struct ds2760_device_info *di)
{
int ret, i, start, count, scale[5];
if (di->update_time && time_before(jiffies, di->update_time +
msecs_to_jiffies(cache_time)))
return 0;
/* The first time we read the entire contents of SRAM/EEPROM,
* but after that we just read the interesting bits that change. */
if (di->update_time == 0) {
start = 0;
count = DS2760_DATA_SIZE;
} else {
start = DS2760_VOLTAGE_MSB;
count = DS2760_TEMP_LSB - start + 1;
}
ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count);
if (ret != count) {
dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n",
di->w1_dev);
return 1;
}
di->update_time = jiffies;
/* DS2760 reports voltage in units of 4.88mV, but the battery class
* reports in units of uV, so convert by multiplying by 4880. */
di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) |
(di->raw[DS2760_VOLTAGE_LSB] >> 5);
di->voltage_uV = di->voltage_raw * 4880;
/* DS2760 reports current in signed units of 0.625mA, but the battery
* class reports in units of µA, so convert by multiplying by 625. */
di->current_raw =
(((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) |
(di->raw[DS2760_CURRENT_LSB] >> 3);
di->current_uA = di->current_raw * 625;
/* DS2760 reports accumulated current in signed units of 0.25mAh. */
di->accum_current_raw =
(((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) |
di->raw[DS2760_CURRENT_ACCUM_LSB];
di->accum_current_uAh = di->accum_current_raw * 250;
/* DS2760 reports temperature in signed units of 0.125°C, but the
* battery class reports in units of 1/10 °C, so we convert by
* multiplying by .125 * 10 = 1.25. */
di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) |
(di->raw[DS2760_TEMP_LSB] >> 5);
di->temp_C = di->temp_raw + (di->temp_raw / 4);
/* At least some battery monitors (e.g. HP iPAQ) store the battery's
* maximum rated capacity. */
if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities))
di->rated_capacity = rated_capacities[
(unsigned int)di->raw[DS2760_RATED_CAPACITY]];
else
di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10;
di->rated_capacity *= 1000; /* convert to µAh */
/* Calculate the full level at the present temperature. */
di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 |
di->raw[DS2760_ACTIVE_FULL + 1];
scale[0] = di->raw[DS2760_ACTIVE_FULL] << 8 |
di->raw[DS2760_ACTIVE_FULL + 1];
for (i = 1; i < 5; i++)
scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 2 + i];
di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10);
di->full_active_uAh *= 1000; /* convert to µAh */
/* Calculate the empty level at the present temperature. */
scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4];
for (i = 3; i >= 0; i--)
scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i];
di->empty_uAh = battery_interpolate(scale, di->temp_C / 10);
di->empty_uAh *= 1000; /* convert to µAh */
/* From Maxim Application Note 131: remaining capacity =
* ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */
di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) /
(di->full_active_uAh - di->empty_uAh);
if (di->rem_capacity < 0)
di->rem_capacity = 0;
if (di->rem_capacity > 100)
di->rem_capacity = 100;
if (di->current_uA)
di->life_sec = -((di->accum_current_uAh - di->empty_uAh) *
3600L) / di->current_uA;
else
di->life_sec = 0;
return 0;
}
static void ds2760_battery_update_status(struct ds2760_device_info *di)
{
int old_charge_status = di->charge_status;
ds2760_battery_read_status(di);
if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN)
di->full_counter = 0;
if (power_supply_am_i_supplied(&di->bat)) {
if (di->current_uA > 10000) {
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
di->full_counter = 0;
} else if (di->current_uA < -5000) {
if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING)
dev_notice(di->dev, "not enough power to "
"charge\n");
di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
di->full_counter = 0;
} else if (di->current_uA < 10000 &&
di->charge_status != POWER_SUPPLY_STATUS_FULL) {
/* Don't consider the battery to be full unless
* we've seen the current < 10 mA at least two
* consecutive times. */
di->full_counter++;
if (di->full_counter < 2) {
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
} else {
unsigned char acr[2];
int acr_val;
/* acr is in units of 0.25 mAh */
acr_val = di->full_active_uAh * 4L / 1000;
acr[0] = acr_val >> 8;
acr[1] = acr_val & 0xff;
if (w1_ds2760_write(di->w1_dev, acr,
DS2760_CURRENT_ACCUM_MSB, 2) < 2)
dev_warn(di->dev,
"ACR reset failed\n");
di->charge_status = POWER_SUPPLY_STATUS_FULL;
}
}
} else {
di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
di->full_counter = 0;
}
if (di->charge_status != old_charge_status)
power_supply_changed(&di->bat);
return;
}
static void ds2760_battery_work(struct work_struct *work)
{
struct ds2760_device_info *di = container_of(work,
struct ds2760_device_info, monitor_work.work);
const int interval = HZ * 60;
dev_dbg(di->dev, "%s\n", __FUNCTION__);
ds2760_battery_update_status(di);
queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval);
return;
}
#define to_ds2760_device_info(x) container_of((x), struct ds2760_device_info, \
bat);
static void ds2760_battery_external_power_changed(struct power_supply *psy)
{
struct ds2760_device_info *di = to_ds2760_device_info(psy);
dev_dbg(di->dev, "%s\n", __FUNCTION__);
cancel_delayed_work(&di->monitor_work);
queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10);
return;
}
static int ds2760_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct ds2760_device_info *di = to_ds2760_device_info(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = di->charge_status;
return 0;
default:
break;
}
ds2760_battery_read_status(di);
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = di->voltage_uV;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = di->current_uA;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = di->rated_capacity;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = di->full_active_uAh;
break;
case POWER_SUPPLY_PROP_CHARGE_EMPTY:
val->intval = di->empty_uAh;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval = di->accum_current_uAh;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = di->temp_C;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property ds2760_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_TEMP,
};
static int ds2760_battery_probe(struct platform_device *pdev)
{
int retval = 0;
struct ds2760_device_info *di;
struct ds2760_platform_data *pdata;
di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di) {
retval = -ENOMEM;
goto di_alloc_failed;
}
platform_set_drvdata(pdev, di);
pdata = pdev->dev.platform_data;
di->dev = &pdev->dev;
di->w1_dev = pdev->dev.parent;
di->bat.name = pdev->dev.bus_id;
di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
di->bat.properties = ds2760_battery_props;
di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props);
di->bat.get_property = ds2760_battery_get_property;
di->bat.external_power_changed =
ds2760_battery_external_power_changed;
di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
retval = power_supply_register(&pdev->dev, &di->bat);
if (retval) {
dev_err(di->dev, "failed to register battery");
goto batt_failed;
}
INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work);
di->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id);
if (!di->monitor_wqueue) {
retval = -ESRCH;
goto workqueue_failed;
}
queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1);
goto success;
workqueue_failed:
power_supply_unregister(&di->bat);
batt_failed:
kfree(di);
di_alloc_failed:
success:
return retval;
}
static int ds2760_battery_remove(struct platform_device *pdev)
{
struct ds2760_device_info *di = platform_get_drvdata(pdev);
cancel_rearming_delayed_workqueue(di->monitor_wqueue,
&di->monitor_work);
destroy_workqueue(di->monitor_wqueue);
power_supply_unregister(&di->bat);
return 0;
}
#ifdef CONFIG_PM
static int ds2760_battery_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct ds2760_device_info *di = platform_get_drvdata(pdev);
di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
return 0;
}
static int ds2760_battery_resume(struct platform_device *pdev)
{
struct ds2760_device_info *di = platform_get_drvdata(pdev);
di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
power_supply_changed(&di->bat);
cancel_delayed_work(&di->monitor_work);
queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ);
return 0;
}
#else
#define ds2760_battery_suspend NULL
#define ds2760_battery_resume NULL
#endif /* CONFIG_PM */
static struct platform_driver ds2760_battery_driver = {
.driver = {
.name = "ds2760-battery",
},
.probe = ds2760_battery_probe,
.remove = ds2760_battery_remove,
.suspend = ds2760_battery_suspend,
.resume = ds2760_battery_resume,
};
static int __init ds2760_battery_init(void)
{
return platform_driver_register(&ds2760_battery_driver);
}
static void __exit ds2760_battery_exit(void)
{
platform_driver_unregister(&ds2760_battery_driver);
return;
}
module_init(ds2760_battery_init);
module_exit(ds2760_battery_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, "
"Matt Reimer <mreimer@vpop.net>, "
"Anton Vorontsov <cbou@mail.ru>");
MODULE_DESCRIPTION("ds2760 battery driver");
/*
* Battery driver for One Laptop Per Child board.
*
* Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
*
* 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.
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <asm/olpc.h>
#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */
#define EC_BAT_ACR 0x12
#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */
#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */
#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */
#define EC_BAT_SOC 0x16 /* uint8_t, percentage */
#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */
#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */
#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */
#define BAT_STAT_PRESENT 0x01
#define BAT_STAT_FULL 0x02
#define BAT_STAT_LOW 0x04
#define BAT_STAT_DESTROY 0x08
#define BAT_STAT_AC 0x10
#define BAT_STAT_CHARGING 0x20
#define BAT_STAT_DISCHARGING 0x40
#define BAT_ERR_INFOFAIL 0x02
#define BAT_ERR_OVERVOLTAGE 0x04
#define BAT_ERR_OVERTEMP 0x05
#define BAT_ERR_GAUGESTOP 0x06
#define BAT_ERR_OUT_OF_CONTROL 0x07
#define BAT_ERR_ID_FAIL 0x09
#define BAT_ERR_ACR_FAIL 0x10
#define BAT_ADDR_MFR_TYPE 0x5F
/*********************************************************************
* Power
*********************************************************************/
static int olpc_ac_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret = 0;
uint8_t status;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
if (ret)
return ret;
val->intval = !!(status & BAT_STAT_AC);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property olpc_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static struct power_supply olpc_ac = {
.name = "olpc-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = olpc_ac_props,
.num_properties = ARRAY_SIZE(olpc_ac_props),
.get_property = olpc_ac_get_prop,
};
/*********************************************************************
* Battery properties
*********************************************************************/
static int olpc_bat_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret = 0;
int16_t ec_word;
uint8_t ec_byte;
ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1);
if (ret)
return ret;
/* Theoretically there's a race here -- the battery could be
removed immediately after we check whether it's present, and
then we query for some other property of the now-absent battery.
It doesn't matter though -- the EC will return the last-known
information, and it's as if we just ran that _little_ bit faster
and managed to read it out before the battery went away. */
if (!(ec_byte & BAT_STAT_PRESENT) && psp != POWER_SUPPLY_PROP_PRESENT)
return -ENODEV;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (olpc_platform_info.ecver > 0x44) {
if (ec_byte & BAT_STAT_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (ec_byte & BAT_STAT_DISCHARGING)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (ec_byte & BAT_STAT_FULL)
val->intval = POWER_SUPPLY_STATUS_FULL;
else /* er,... */
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
} else {
/* Older EC didn't report charge/discharge bits */
if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (ec_byte & BAT_STAT_FULL)
val->intval = POWER_SUPPLY_STATUS_FULL;
else /* Not _necessarily_ true but EC doesn't tell all yet */
val->intval = POWER_SUPPLY_STATUS_CHARGING;
break;
}
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(ec_byte & BAT_STAT_PRESENT);
break;
case POWER_SUPPLY_PROP_HEALTH:
if (ec_byte & BAT_STAT_DESTROY)
val->intval = POWER_SUPPLY_HEALTH_DEAD;
else {
ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
if (ret)
return ret;
switch (ec_byte) {
case 0:
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
case BAT_ERR_OVERTEMP:
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
case BAT_ERR_OVERVOLTAGE:
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
break;
case BAT_ERR_INFOFAIL:
case BAT_ERR_OUT_OF_CONTROL:
case BAT_ERR_ID_FAIL:
case BAT_ERR_ACR_FAIL:
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
default:
/* Eep. We don't know this failure code */
return -EIO;
}
}
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
ec_byte = BAT_ADDR_MFR_TYPE;
ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
if (ret)
return ret;
switch (ec_byte >> 4) {
case 1:
val->strval = "Gold Peak";
break;
case 2:
val->strval = "BYD";
break;
default:
val->strval = "Unknown";
break;
}
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
ec_byte = BAT_ADDR_MFR_TYPE;
ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
if (ret)
return ret;
switch (ec_byte & 0xf) {
case 1:
val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
break;
case 2:
val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
break;
default:
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
break;
}
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2);
if (ret)
return ret;
ec_word = be16_to_cpu(ec_word);
val->intval = ec_word * 9760L / 32;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2);
if (ret)
return ret;
ec_word = be16_to_cpu(ec_word);
val->intval = ec_word * 15625L / 120;
break;
case POWER_SUPPLY_PROP_CAPACITY:
ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
if (ret)
return ret;
val->intval = ec_byte;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
if (ec_byte & BAT_STAT_FULL)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
else if (ec_byte & BAT_STAT_LOW)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
else
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
break;
case POWER_SUPPLY_PROP_TEMP:
ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2);
if (ret)
return ret;
ec_word = be16_to_cpu(ec_word);
val->intval = ec_word * 100 / 256;
break;
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
if (ret)
return ret;
ec_word = be16_to_cpu(ec_word);
val->intval = ec_word * 100 / 256;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property olpc_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
POWER_SUPPLY_PROP_MANUFACTURER,
};
/*********************************************************************
* Initialisation
*********************************************************************/
static struct platform_device *bat_pdev;
static struct power_supply olpc_bat = {
.properties = olpc_bat_props,
.num_properties = ARRAY_SIZE(olpc_bat_props),
.get_property = olpc_bat_get_property,
.use_for_apm = 1,
};
void olpc_battery_trigger_uevent(unsigned long cause)
{
if (cause & EC_SCI_SRC_ACPWR)
kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE);
if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY))
kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE);
}
static int __init olpc_bat_init(void)
{
int ret = 0;
uint8_t status;
if (!olpc_platform_info.ecver)
return -ENXIO;
if (olpc_platform_info.ecver < 0x43) {
printk(KERN_NOTICE "OLPC EC version 0x%02x too old for battery driver.\n", olpc_platform_info.ecver);
return -ENXIO;
}
ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
if (ret)
return ret;
/* Ignore the status. It doesn't actually matter */
bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0);
if (IS_ERR(bat_pdev))
return PTR_ERR(bat_pdev);
ret = power_supply_register(&bat_pdev->dev, &olpc_ac);
if (ret)
goto ac_failed;
olpc_bat.name = bat_pdev->name;
ret = power_supply_register(&bat_pdev->dev, &olpc_bat);
if (ret)
goto battery_failed;
olpc_register_battery_callback(&olpc_battery_trigger_uevent);
goto success;
battery_failed:
power_supply_unregister(&olpc_ac);
ac_failed:
platform_device_unregister(bat_pdev);
success:
return ret;
}
static void __exit olpc_bat_exit(void)
{
olpc_deregister_battery_callback();
power_supply_unregister(&olpc_bat);
power_supply_unregister(&olpc_ac);
platform_device_unregister(bat_pdev);
return;
}
module_init(olpc_bat_init);
module_exit(olpc_bat_exit);
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");
/*
* Common power driver for PDAs and phones with one or two external
* power supplies (AC/USB) connected to main and backup batteries,
* and optional builtin charger.
*
* Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
*
* 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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/power_supply.h>
#include <linux/pda_power.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
static inline unsigned int get_irq_flags(struct resource *res)
{
unsigned int flags = IRQF_DISABLED | IRQF_SHARED;
flags |= res->flags & IRQF_TRIGGER_MASK;
return flags;
}
static struct device *dev;
static struct pda_power_pdata *pdata;
static struct resource *ac_irq, *usb_irq;
static struct timer_list charger_timer;
static struct timer_list supply_timer;
static int pda_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
if (psy->type == POWER_SUPPLY_TYPE_MAINS)
val->intval = pdata->is_ac_online ?
pdata->is_ac_online() : 0;
else
val->intval = pdata->is_usb_online ?
pdata->is_usb_online() : 0;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property pda_power_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static char *pda_power_supplied_to[] = {
"main-battery",
"backup-battery",
};
static struct power_supply pda_power_supplies[] = {
{
.name = "ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.supplied_to = pda_power_supplied_to,
.num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
.properties = pda_power_props,
.num_properties = ARRAY_SIZE(pda_power_props),
.get_property = pda_power_get_property,
},
{
.name = "usb",
.type = POWER_SUPPLY_TYPE_USB,
.supplied_to = pda_power_supplied_to,
.num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
.properties = pda_power_props,
.num_properties = ARRAY_SIZE(pda_power_props),
.get_property = pda_power_get_property,
},
};
static void update_charger(void)
{
if (!pdata->set_charge)
return;
if (pdata->is_ac_online && pdata->is_ac_online()) {
dev_dbg(dev, "charger on (AC)\n");
pdata->set_charge(PDA_POWER_CHARGE_AC);
} else if (pdata->is_usb_online && pdata->is_usb_online()) {
dev_dbg(dev, "charger on (USB)\n");
pdata->set_charge(PDA_POWER_CHARGE_USB);
} else {
dev_dbg(dev, "charger off\n");
pdata->set_charge(0);
}
return;
}
static void supply_timer_func(unsigned long irq)
{
if (ac_irq && irq == ac_irq->start)
power_supply_changed(&pda_power_supplies[0]);
else if (usb_irq && irq == usb_irq->start)
power_supply_changed(&pda_power_supplies[1]);
return;
}
static void charger_timer_func(unsigned long irq)
{
update_charger();
/* Okay, charger set. Now wait a bit before notifying supplicants,
* charge power should stabilize. */
supply_timer.data = irq;
mod_timer(&supply_timer,
jiffies + msecs_to_jiffies(pdata->wait_for_charger));
return;
}
static irqreturn_t power_changed_isr(int irq, void *unused)
{
/* Wait a bit before reading ac/usb line status and setting charger,
* because ac/usb status readings may lag from irq. */
charger_timer.data = irq;
mod_timer(&charger_timer,
jiffies + msecs_to_jiffies(pdata->wait_for_status));
return IRQ_HANDLED;
}
static int pda_power_probe(struct platform_device *pdev)
{
int ret = 0;
dev = &pdev->dev;
if (pdev->id != -1) {
dev_err(dev, "it's meaningless to register several "
"pda_powers; use id = -1\n");
ret = -EINVAL;
goto wrongid;
}
pdata = pdev->dev.platform_data;
update_charger();
if (!pdata->wait_for_status)
pdata->wait_for_status = 500;
if (!pdata->wait_for_charger)
pdata->wait_for_charger = 500;
setup_timer(&charger_timer, charger_timer_func, 0);
setup_timer(&supply_timer, supply_timer_func, 0);
ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");
if (!ac_irq && !usb_irq) {
dev_err(dev, "no ac/usb irq specified\n");
ret = -ENODEV;
goto noirqs;
}
if (pdata->supplied_to) {
pda_power_supplies[0].supplied_to = pdata->supplied_to;
pda_power_supplies[1].supplied_to = pdata->supplied_to;
pda_power_supplies[0].num_supplicants = pdata->num_supplicants;
pda_power_supplies[1].num_supplicants = pdata->num_supplicants;
}
ret = power_supply_register(&pdev->dev, &pda_power_supplies[0]);
if (ret) {
dev_err(dev, "failed to register %s power supply\n",
pda_power_supplies[0].name);
goto supply0_failed;
}
ret = power_supply_register(&pdev->dev, &pda_power_supplies[1]);
if (ret) {
dev_err(dev, "failed to register %s power supply\n",
pda_power_supplies[1].name);
goto supply1_failed;
}
if (ac_irq) {
ret = request_irq(ac_irq->start, power_changed_isr,
get_irq_flags(ac_irq), ac_irq->name,
&pda_power_supplies[0]);
if (ret) {
dev_err(dev, "request ac irq failed\n");
goto ac_irq_failed;
}
}
if (usb_irq) {
ret = request_irq(usb_irq->start, power_changed_isr,
get_irq_flags(usb_irq), usb_irq->name,
&pda_power_supplies[1]);
if (ret) {
dev_err(dev, "request usb irq failed\n");
goto usb_irq_failed;
}
}
goto success;
usb_irq_failed:
if (ac_irq)
free_irq(ac_irq->start, &pda_power_supplies[0]);
ac_irq_failed:
power_supply_unregister(&pda_power_supplies[1]);
supply1_failed:
power_supply_unregister(&pda_power_supplies[0]);
supply0_failed:
noirqs:
wrongid:
success:
return ret;
}
static int pda_power_remove(struct platform_device *pdev)
{
if (usb_irq)
free_irq(usb_irq->start, &pda_power_supplies[1]);
if (ac_irq)
free_irq(ac_irq->start, &pda_power_supplies[0]);
del_timer_sync(&charger_timer);
del_timer_sync(&supply_timer);
power_supply_unregister(&pda_power_supplies[1]);
power_supply_unregister(&pda_power_supplies[0]);
return 0;
}
static struct platform_driver pda_power_pdrv = {
.driver = {
.name = "pda-power",
},
.probe = pda_power_probe,
.remove = pda_power_remove,
};
static int __init pda_power_init(void)
{
return platform_driver_register(&pda_power_pdrv);
}
static void __exit pda_power_exit(void)
{
platform_driver_unregister(&pda_power_pdrv);
return;
}
module_init(pda_power_init);
module_exit(pda_power_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
/*
* Battery class driver for Apple PMU
*
* Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
*
* 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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/adb.h>
#include <linux/pmu.h>
static struct pmu_battery_dev {
struct power_supply bat;
struct pmu_battery_info *pbi;
char name[16];
int propval;
} *pbats[PMU_MAX_BATTERIES];
#define to_pmu_battery_dev(x) container_of(x, struct pmu_battery_dev, bat)
/*********************************************************************
* Power
*********************************************************************/
static int pmu_get_ac_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) ||
(pmu_battery_count == 0);
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property pmu_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static struct power_supply pmu_ac = {
.name = "pmu-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = pmu_ac_props,
.num_properties = ARRAY_SIZE(pmu_ac_props),
.get_property = pmu_get_ac_prop,
};
/*********************************************************************
* Battery properties
*********************************************************************/
static char *pmu_batt_types[] = {
"Smart", "Comet", "Hooper", "Unknown"
};
static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi)
{
switch (pbi->flags & PMU_BATT_TYPE_MASK) {
case PMU_BATT_TYPE_SMART:
return pmu_batt_types[0];
case PMU_BATT_TYPE_COMET:
return pmu_batt_types[1];
case PMU_BATT_TYPE_HOOPER:
return pmu_batt_types[2];
default: break;
}
return pmu_batt_types[3];
}
static int pmu_bat_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy);
struct pmu_battery_info *pbi = pbat->pbi;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (pbi->flags & PMU_BATT_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(pbi->flags & PMU_BATT_PRESENT);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = pmu_bat_get_model_name(pbi);
break;
case POWER_SUPPLY_PROP_ENERGY_AVG:
val->intval = pbi->charge * 1000; /* mWh -> µWh */
break;
case POWER_SUPPLY_PROP_ENERGY_FULL:
val->intval = pbi->max_charge * 1000; /* mWh -> µWh */
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
val->intval = pbi->amperage * 1000; /* mA -> µA */
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
val->intval = pbi->voltage * 1000; /* mV -> µV */
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
val->intval = pbi->time_remaining;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property pmu_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_ENERGY_AVG,
POWER_SUPPLY_PROP_ENERGY_FULL,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
};
/*********************************************************************
* Initialisation
*********************************************************************/
static struct platform_device *bat_pdev;
static int __init pmu_bat_init(void)
{
int ret;
int i;
bat_pdev = platform_device_register_simple("pmu-battery",
0, NULL, 0);
if (IS_ERR(bat_pdev)) {
ret = PTR_ERR(bat_pdev);
goto pdev_register_failed;
}
ret = power_supply_register(&bat_pdev->dev, &pmu_ac);
if (ret)
goto ac_register_failed;
for (i = 0; i < pmu_battery_count; i++) {
struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat),
GFP_KERNEL);
if (!pbat)
break;
sprintf(pbat->name, "PMU battery %d", i);
pbat->bat.name = pbat->name;
pbat->bat.properties = pmu_bat_props;
pbat->bat.num_properties = ARRAY_SIZE(pmu_bat_props);
pbat->bat.get_property = pmu_bat_get_property;
pbat->pbi = &pmu_batteries[i];
ret = power_supply_register(&bat_pdev->dev, &pbat->bat);
if (ret) {
kfree(pbat);
goto battery_register_failed;
}
pbats[i] = pbat;
}
goto success;
battery_register_failed:
while (i--) {
if (!pbats[i])
continue;
power_supply_unregister(&pbats[i]->bat);
kfree(pbats[i]);
}
power_supply_unregister(&pmu_ac);
ac_register_failed:
platform_device_unregister(bat_pdev);
pdev_register_failed:
success:
return ret;
}
static void __exit pmu_bat_exit(void)
{
int i;
for (i = 0; i < PMU_MAX_BATTERIES; i++) {
if (!pbats[i])
continue;
power_supply_unregister(&pbats[i]->bat);
kfree(pbats[i]);
}
power_supply_unregister(&pmu_ac);
platform_device_unregister(bat_pdev);
return;
}
module_init(pmu_bat_init);
module_exit(pmu_bat_exit);
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PMU battery driver");
/*
* Functions private to power supply 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
*/
#ifdef CONFIG_SYSFS
extern int power_supply_create_attrs(struct power_supply *psy);
extern void power_supply_remove_attrs(struct power_supply *psy);
extern int power_supply_uevent(struct device *dev, char **envp, int num_envp,
char *buffer, int buffer_size);
#else
static inline int power_supply_create_attrs(struct power_supply *psy)
{ return 0; }
static inline void power_supply_remove_attrs(struct power_supply *psy) {}
#define power_supply_uevent NULL
#endif /* CONFIG_SYSFS */
#ifdef CONFIG_LEDS_TRIGGERS
extern void power_supply_update_leds(struct power_supply *psy);
extern int power_supply_create_triggers(struct power_supply *psy);
extern void power_supply_remove_triggers(struct power_supply *psy);
#else
static inline void power_supply_update_leds(struct power_supply *psy) {}
static inline int power_supply_create_triggers(struct power_supply *psy)
{ return 0; }
static inline void power_supply_remove_triggers(struct power_supply *psy) {}
#endif /* CONFIG_LEDS_TRIGGERS */
/*
* 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/device.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include "power_supply.h"
struct class *power_supply_class;
static void power_supply_changed_work(struct work_struct *work)
{
struct power_supply *psy = container_of(work, struct power_supply,
changed_work);
int i;
dev_dbg(psy->dev, "%s\n", __FUNCTION__);
for (i = 0; i < psy->num_supplicants; i++) {
struct device *dev;
down(&power_supply_class->sem);
list_for_each_entry(dev, &power_supply_class->devices, node) {
struct power_supply *pst = dev_get_drvdata(dev);
if (!strcmp(psy->supplied_to[i], pst->name)) {
if (pst->external_power_changed)
pst->external_power_changed(pst);
}
}
up(&power_supply_class->sem);
}
power_supply_update_leds(psy);
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
return;
}
void power_supply_changed(struct power_supply *psy)
{
dev_dbg(psy->dev, "%s\n", __FUNCTION__);
schedule_work(&psy->changed_work);
return;
}
int power_supply_am_i_supplied(struct power_supply *psy)
{
union power_supply_propval ret = {0,};
struct device *dev;
down(&power_supply_class->sem);
list_for_each_entry(dev, &power_supply_class->devices, node) {
struct power_supply *epsy = dev_get_drvdata(dev);
int i;
for (i = 0; i < epsy->num_supplicants; i++) {
if (!strcmp(epsy->supplied_to[i], psy->name)) {
if (epsy->get_property(epsy,
POWER_SUPPLY_PROP_ONLINE, &ret))
continue;
if (ret.intval)
goto out;
}
}
}
out:
up(&power_supply_class->sem);
dev_dbg(psy->dev, "%s %d\n", __FUNCTION__, ret.intval);
return ret.intval;
}
int power_supply_register(struct device *parent, struct power_supply *psy)
{
int rc = 0;
psy->dev = device_create(power_supply_class, parent, 0,
"%s", psy->name);
if (IS_ERR(psy->dev)) {
rc = PTR_ERR(psy->dev);
goto dev_create_failed;
}
dev_set_drvdata(psy->dev, psy);
INIT_WORK(&psy->changed_work, power_supply_changed_work);
rc = power_supply_create_attrs(psy);
if (rc)
goto create_attrs_failed;
rc = power_supply_create_triggers(psy);
if (rc)
goto create_triggers_failed;
power_supply_changed(psy);
goto success;
create_triggers_failed:
power_supply_remove_attrs(psy);
create_attrs_failed:
device_unregister(psy->dev);
dev_create_failed:
success:
return rc;
}
void power_supply_unregister(struct power_supply *psy)
{
flush_scheduled_work();
power_supply_remove_triggers(psy);
power_supply_remove_attrs(psy);
device_unregister(psy->dev);
return;
}
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;
return 0;
}
static void __exit power_supply_class_exit(void)
{
class_destroy(power_supply_class);
return;
}
EXPORT_SYMBOL_GPL(power_supply_changed);
EXPORT_SYMBOL_GPL(power_supply_am_i_supplied);
EXPORT_SYMBOL_GPL(power_supply_register);
EXPORT_SYMBOL_GPL(power_supply_unregister);
/* exported for the APM Power driver, APM emulation */
EXPORT_SYMBOL_GPL(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");
/*
* LEDs triggers for power supply 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/power_supply.h>
/* Battery specific LEDs triggers. */
static void power_supply_update_bat_leds(struct power_supply *psy)
{
union power_supply_propval status;
if (psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
return;
dev_dbg(psy->dev, "%s %d\n", __FUNCTION__, status.intval);
switch (status.intval) {
case POWER_SUPPLY_STATUS_FULL:
led_trigger_event(psy->charging_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_OFF);
led_trigger_event(psy->full_trig, LED_FULL);
break;
case POWER_SUPPLY_STATUS_CHARGING:
led_trigger_event(psy->charging_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_FULL);
led_trigger_event(psy->full_trig, LED_OFF);
break;
default:
led_trigger_event(psy->charging_full_trig, LED_OFF);
led_trigger_event(psy->charging_trig, LED_OFF);
led_trigger_event(psy->full_trig, LED_OFF);
break;
}
return;
}
static int power_supply_create_bat_triggers(struct power_supply *psy)
{
int rc = 0;
psy->charging_full_trig_name = kmalloc(strlen(psy->name) +
sizeof("-charging-or-full"), GFP_KERNEL);
if (!psy->charging_full_trig_name)
goto charging_full_failed;
psy->charging_trig_name = kmalloc(strlen(psy->name) +
sizeof("-charging"), GFP_KERNEL);
if (!psy->charging_trig_name)
goto charging_failed;
psy->full_trig_name = kmalloc(strlen(psy->name) +
sizeof("-full"), GFP_KERNEL);
if (!psy->full_trig_name)
goto full_failed;
strcpy(psy->charging_full_trig_name, psy->name);
strcat(psy->charging_full_trig_name, "-charging-or-full");
strcpy(psy->charging_trig_name, psy->name);
strcat(psy->charging_trig_name, "-charging");
strcpy(psy->full_trig_name, psy->name);
strcat(psy->full_trig_name, "-full");
led_trigger_register_simple(psy->charging_full_trig_name,
&psy->charging_full_trig);
led_trigger_register_simple(psy->charging_trig_name,
&psy->charging_trig);
led_trigger_register_simple(psy->full_trig_name,
&psy->full_trig);
goto success;
full_failed:
kfree(psy->charging_trig_name);
charging_failed:
kfree(psy->charging_full_trig_name);
charging_full_failed:
rc = -ENOMEM;
success:
return rc;
}
static void power_supply_remove_bat_triggers(struct power_supply *psy)
{
led_trigger_unregister_simple(psy->charging_full_trig);
led_trigger_unregister_simple(psy->charging_trig);
led_trigger_unregister_simple(psy->full_trig);
kfree(psy->full_trig_name);
kfree(psy->charging_trig_name);
kfree(psy->charging_full_trig_name);
return;
}
/* Generated power specific LEDs triggers. */
static void power_supply_update_gen_leds(struct power_supply *psy)
{
union power_supply_propval online;
if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
return;
dev_dbg(psy->dev, "%s %d\n", __FUNCTION__, online.intval);
if (online.intval)
led_trigger_event(psy->online_trig, LED_FULL);
else
led_trigger_event(psy->online_trig, LED_OFF);
return;
}
static int power_supply_create_gen_triggers(struct power_supply *psy)
{
int rc = 0;
psy->online_trig_name = kmalloc(strlen(psy->name) + sizeof("-online"),
GFP_KERNEL);
if (!psy->online_trig_name)
goto online_failed;
strcpy(psy->online_trig_name, psy->name);
strcat(psy->online_trig_name, "-online");
led_trigger_register_simple(psy->online_trig_name, &psy->online_trig);
goto success;
online_failed:
rc = -ENOMEM;
success:
return rc;
}
static void power_supply_remove_gen_triggers(struct power_supply *psy)
{
led_trigger_unregister_simple(psy->online_trig);
kfree(psy->online_trig_name);
return;
}
/* Choice what triggers to create&update. */
void power_supply_update_leds(struct power_supply *psy)
{
if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
power_supply_update_bat_leds(psy);
else
power_supply_update_gen_leds(psy);
return;
}
int power_supply_create_triggers(struct power_supply *psy)
{
if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
return power_supply_create_bat_triggers(psy);
return power_supply_create_gen_triggers(psy);
}
void power_supply_remove_triggers(struct power_supply *psy)
{
if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
power_supply_remove_bat_triggers(psy);
else
power_supply_remove_gen_triggers(psy);
return;
}
/*
* 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>
/*
* 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"
};
static char *capacity_level_text[] = {
"Unknown", "Critical", "Low", "Normal", "High", "Full"
};
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_CAPACITY_LEVEL)
return sprintf(buf, "%s\n",
capacity_level_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]]);
return;
}
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, char **envp, int num_envp,
char *buffer, int buffer_size)
{
struct power_supply *psy = dev_get_drvdata(dev);
int i = 0, length = 0, 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(envp, num_envp, &i, buffer, buffer_size,
&length, "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(envp, num_envp, &i, buffer, buffer_size,
&length, "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(envp, num_envp, &i, buffer, buffer_size,
&length, "POWER_SUPPLY_%s=%s",
attrname, prop_buf);
kfree(attrname);
if (ret)
goto out;
}
out:
free_page((unsigned long)prop_buf);
return ret;
}
...@@ -35,4 +35,17 @@ config W1_SLAVE_DS2433_CRC ...@@ -35,4 +35,17 @@ config W1_SLAVE_DS2433_CRC
Each block has 30 bytes of data and a two byte CRC16. Each block has 30 bytes of data and a two byte CRC16.
Full block writes are only allowed if the CRC is valid. Full block writes are only allowed if the CRC is valid.
config W1_SLAVE_DS2760
tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)"
depends on W1
help
If you enable this you will have the DS2760 battery monitor
chip support.
The battery monitor chip is used in many batteries/devices
as the one who is responsible for charging/discharging/monitoring
Li+ batteries.
If you are unsure, say N.
endmenu endmenu
...@@ -5,4 +5,5 @@ ...@@ -5,4 +5,5 @@
obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o
obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o
obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o
obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o
/*
* 1-Wire implementation for the ds2760 chip
*
* Copyright © 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/idr.h>
#include "../w1.h"
#include "../w1_int.h"
#include "../w1_family.h"
#include "w1_ds2760.h"
static int w1_ds2760_io(struct device *dev, char *buf, int addr, size_t count,
int io)
{
struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
if (!dev)
return 0;
mutex_lock(&sl->master->mutex);
if (addr > DS2760_DATA_SIZE || addr < 0) {
count = 0;
goto out;
}
if (addr + count > DS2760_DATA_SIZE)
count = DS2760_DATA_SIZE - addr;
if (!w1_reset_select_slave(sl)) {
if (!io) {
w1_write_8(sl->master, W1_DS2760_READ_DATA);
w1_write_8(sl->master, addr);
count = w1_read_block(sl->master, buf, count);
} else {
w1_write_8(sl->master, W1_DS2760_WRITE_DATA);
w1_write_8(sl->master, addr);
w1_write_block(sl->master, buf, count);
/* XXX w1_write_block returns void, not n_written */
}
}
out:
mutex_unlock(&sl->master->mutex);
return count;
}
int w1_ds2760_read(struct device *dev, char *buf, int addr, size_t count)
{
return w1_ds2760_io(dev, buf, addr, count, 0);
}
int w1_ds2760_write(struct device *dev, char *buf, int addr, size_t count)
{
return w1_ds2760_io(dev, buf, addr, count, 1);
}
static ssize_t w1_ds2760_read_bin(struct kobject *kobj, char *buf, loff_t off,
size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
return w1_ds2760_read(dev, buf, off, count);
}
static struct bin_attribute w1_ds2760_bin_attr = {
.attr = {
.name = "w1_slave",
.mode = S_IRUGO,
.owner = THIS_MODULE,
},
.size = DS2760_DATA_SIZE,
.read = w1_ds2760_read_bin,
};
static DEFINE_IDR(bat_idr);
static DEFINE_MUTEX(bat_idr_lock);
static int new_bat_id(void)
{
int ret;
while (1) {
int id;
ret = idr_pre_get(&bat_idr, GFP_KERNEL);
if (ret == 0)
return -ENOMEM;
mutex_lock(&bat_idr_lock);
ret = idr_get_new(&bat_idr, NULL, &id);
mutex_unlock(&bat_idr_lock);
if (ret == 0) {
ret = id & MAX_ID_MASK;
break;
} else if (ret == -EAGAIN) {
continue;
} else {
break;
}
}
return ret;
}
static void release_bat_id(int id)
{
mutex_lock(&bat_idr_lock);
idr_remove(&bat_idr, id);
mutex_unlock(&bat_idr_lock);
return;
}
static int w1_ds2760_add_slave(struct w1_slave *sl)
{
int ret;
int id;
struct platform_device *pdev;
id = new_bat_id();
if (id < 0) {
ret = id;
goto noid;
}
pdev = platform_device_alloc("ds2760-battery", id);
if (!pdev) {
ret = -ENOMEM;
goto pdev_alloc_failed;
}
pdev->dev.parent = &sl->dev;
ret = platform_device_add(pdev);
if (ret)
goto pdev_add_failed;
ret = sysfs_create_bin_file(&sl->dev.kobj, &w1_ds2760_bin_attr);
if (ret)
goto bin_attr_failed;
dev_set_drvdata(&sl->dev, pdev);
goto success;
bin_attr_failed:
pdev_add_failed:
platform_device_unregister(pdev);
pdev_alloc_failed:
release_bat_id(id);
noid:
success:
return ret;
}
static void w1_ds2760_remove_slave(struct w1_slave *sl)
{
struct platform_device *pdev = dev_get_drvdata(&sl->dev);
int id = pdev->id;
platform_device_unregister(pdev);
release_bat_id(id);
sysfs_remove_bin_file(&sl->dev.kobj, &w1_ds2760_bin_attr);
return;
}
static struct w1_family_ops w1_ds2760_fops = {
.add_slave = w1_ds2760_add_slave,
.remove_slave = w1_ds2760_remove_slave,
};
static struct w1_family w1_ds2760_family = {
.fid = W1_FAMILY_DS2760,
.fops = &w1_ds2760_fops,
};
static int __init w1_ds2760_init(void)
{
printk(KERN_INFO "1-Wire driver for the DS2760 battery monitor "
" chip - (c) 2004-2005, Szabolcs Gyurko\n");
idr_init(&bat_idr);
return w1_register_family(&w1_ds2760_family);
}
static void __exit w1_ds2760_exit(void)
{
w1_unregister_family(&w1_ds2760_family);
idr_destroy(&bat_idr);
}
EXPORT_SYMBOL(w1_ds2760_read);
EXPORT_SYMBOL(w1_ds2760_write);
module_init(w1_ds2760_init);
module_exit(w1_ds2760_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>");
MODULE_DESCRIPTION("1-wire Driver Dallas 2760 battery monitor chip");
/*
* 1-Wire implementation for the ds2760 chip
*
* Copyright © 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
*/
#ifndef __w1_ds2760_h__
#define __w1_ds2760_h__
/* Known commands to the DS2760 chip */
#define W1_DS2760_SWAP 0xAA
#define W1_DS2760_READ_DATA 0x69
#define W1_DS2760_WRITE_DATA 0x6C
#define W1_DS2760_COPY_DATA 0x48
#define W1_DS2760_RECALL_DATA 0xB8
#define W1_DS2760_LOCK 0x6A
/* Number of valid register addresses */
#define DS2760_DATA_SIZE 0x40
#define DS2760_PROTECTION_REG 0x00
#define DS2760_STATUS_REG 0x01
#define DS2760_EEPROM_REG 0x07
#define DS2760_SPECIAL_FEATURE_REG 0x08
#define DS2760_VOLTAGE_MSB 0x0c
#define DS2760_VOLTAGE_LSB 0x0d
#define DS2760_CURRENT_MSB 0x0e
#define DS2760_CURRENT_LSB 0x0f
#define DS2760_CURRENT_ACCUM_MSB 0x10
#define DS2760_CURRENT_ACCUM_LSB 0x11
#define DS2760_TEMP_MSB 0x18
#define DS2760_TEMP_LSB 0x19
#define DS2760_EEPROM_BLOCK0 0x20
#define DS2760_ACTIVE_FULL 0x20
#define DS2760_EEPROM_BLOCK1 0x30
#define DS2760_RATED_CAPACITY 0x32
#define DS2760_CURRENT_OFFSET_BIAS 0x33
#define DS2760_ACTIVE_EMPTY 0x3b
extern int w1_ds2760_read(struct device *dev, char *buf, int addr,
size_t count);
extern int w1_ds2760_write(struct device *dev, char *buf, int addr,
size_t count);
#endif /* !__w1_ds2760_h__ */
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#define W1_THERM_DS1822 0x22 #define W1_THERM_DS1822 0x22
#define W1_EEPROM_DS2433 0x23 #define W1_EEPROM_DS2433 0x23
#define W1_THERM_DS18B20 0x28 #define W1_THERM_DS18B20 0x28
#define W1_FAMILY_DS2760 0x30
#define MAXNAMELEN 32 #define MAXNAMELEN 32
......
/*
* Common power driver for PDAs and phones with one or two external
* power supplies (AC/USB) connected to main and backup batteries,
* and optional builtin charger.
*
* Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
*
* 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.
*/
#ifndef __PDA_POWER_H__
#define __PDA_POWER_H__
#define PDA_POWER_CHARGE_AC (1 << 0)
#define PDA_POWER_CHARGE_USB (1 << 1)
struct pda_power_pdata {
int (*is_ac_online)(void);
int (*is_usb_online)(void);
void (*set_charge)(int flags);
char **supplied_to;
size_t num_supplicants;
unsigned int wait_for_status; /* msecs, default is 500 */
unsigned int wait_for_charger; /* msecs, default is 500 */
};
#endif /* __PDA_POWER_H__ */
/*
* 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
*/
#ifndef __LINUX_POWER_SUPPLY_H__
#define __LINUX_POWER_SUPPLY_H__
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/leds.h>
/*
* All voltages, currents, charges, energies, time and temperatures in uV,
* µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise
* stated. It's driver's job to convert its raw values to units in which
* this class operates.
*/
/*
* For systems where the charger determines the maximum battery capacity
* the min and max fields should be used to present these values to user
* space. Unused/unknown fields will not appear in sysfs.
*/
enum {
POWER_SUPPLY_STATUS_UNKNOWN = 0,
POWER_SUPPLY_STATUS_CHARGING,
POWER_SUPPLY_STATUS_DISCHARGING,
POWER_SUPPLY_STATUS_NOT_CHARGING,
POWER_SUPPLY_STATUS_FULL,
};
enum {
POWER_SUPPLY_HEALTH_UNKNOWN = 0,
POWER_SUPPLY_HEALTH_GOOD,
POWER_SUPPLY_HEALTH_OVERHEAT,
POWER_SUPPLY_HEALTH_DEAD,
POWER_SUPPLY_HEALTH_OVERVOLTAGE,
POWER_SUPPLY_HEALTH_UNSPEC_FAILURE,
};
enum {
POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0,
POWER_SUPPLY_TECHNOLOGY_NiMH,
POWER_SUPPLY_TECHNOLOGY_LION,
POWER_SUPPLY_TECHNOLOGY_LIPO,
POWER_SUPPLY_TECHNOLOGY_LiFe,
POWER_SUPPLY_TECHNOLOGY_NiCd,
};
enum {
POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN = 0,
POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL,
POWER_SUPPLY_CAPACITY_LEVEL_LOW,
POWER_SUPPLY_CAPACITY_LEVEL_NORMAL,
POWER_SUPPLY_CAPACITY_LEVEL_HIGH,
POWER_SUPPLY_CAPACITY_LEVEL_FULL,
};
enum power_supply_property {
/* Properties of type `int' */
POWER_SUPPLY_PROP_STATUS = 0,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_AVG,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
POWER_SUPPLY_PROP_ENERGY_FULL,
POWER_SUPPLY_PROP_ENERGY_EMPTY,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_ENERGY_AVG,
POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
enum power_supply_type {
POWER_SUPPLY_TYPE_BATTERY = 0,
POWER_SUPPLY_TYPE_UPS,
POWER_SUPPLY_TYPE_MAINS,
POWER_SUPPLY_TYPE_USB,
};
union power_supply_propval {
int intval;
const char *strval;
};
struct power_supply {
const char *name;
enum power_supply_type type;
enum power_supply_property *properties;
size_t num_properties;
char **supplied_to;
size_t num_supplicants;
int (*get_property)(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val);
void (*external_power_changed)(struct power_supply *psy);
/* For APM emulation, think legacy userspace. */
int use_for_apm;
/* private */
struct device *dev;
struct work_struct changed_work;
#ifdef CONFIG_LEDS_TRIGGERS
struct led_trigger *charging_full_trig;
char *charging_full_trig_name;
struct led_trigger *charging_trig;
char *charging_trig_name;
struct led_trigger *full_trig;
char *full_trig_name;
struct led_trigger *online_trig;
char *online_trig_name;
#endif
};
/*
* This is recommended structure to specify static power supply parameters.
* Generic one, parametrizable for different power supplies. Power supply
* class itself does not use it, but that's what implementing most platform
* drivers, should try reuse for consistency.
*/
struct power_supply_info {
const char *name;
int technology;
int voltage_max_design;
int voltage_min_design;
int charge_full_design;
int charge_empty_design;
int energy_full_design;
int energy_empty_design;
int use_for_apm;
};
extern void power_supply_changed(struct power_supply *psy);
extern int power_supply_am_i_supplied(struct power_supply *psy);
extern int power_supply_register(struct device *parent,
struct power_supply *psy);
extern void power_supply_unregister(struct power_supply *psy);
/* For APM emulation, think legacy userspace. */
extern struct class *power_supply_class;
#endif /* __LINUX_POWER_SUPPLY_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment