blob: d7a786d5c4a541bfe749e92bd65024c056e7dc2a [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +04002 * battery.c - ACPI Battery Driver (Revision: 2.0)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 *
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +04004 * Copyright (C) 2007 Alexey Starikovskiy <astarikovskiy@suse.de>
5 * Copyright (C) 2004-2007 Vladimir Lebedev <vladimir.p.lebedev@intel.com>
Linus Torvalds1da177e2005-04-16 15:20:36 -07006 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
7 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
8 *
9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or (at
14 * your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
24 *
25 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 */
27
28#include <linux/kernel.h>
29#include <linux/module.h>
30#include <linux/init.h>
31#include <linux/types.h>
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +040032#include <linux/jiffies.h>
Arjan van de Ven0f66af52009-01-10 14:19:05 -050033#include <linux/async.h>
Hector Martinbc76f902009-08-06 15:57:48 -070034#include <linux/dmi.h>
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040035
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +030036#ifdef CONFIG_ACPI_PROCFS_POWER
Linus Torvalds1da177e2005-04-16 15:20:36 -070037#include <linux/proc_fs.h>
38#include <linux/seq_file.h>
39#include <asm/uaccess.h>
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040040#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -070041
42#include <acpi/acpi_bus.h>
43#include <acpi/acpi_drivers.h>
44
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -050045#ifdef CONFIG_ACPI_SYSFS_POWER
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040046#include <linux/power_supply.h>
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -050047#endif
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040048
Linus Torvalds1da177e2005-04-16 15:20:36 -070049#define ACPI_BATTERY_VALUE_UNKNOWN 0xFFFFFFFF
50
Linus Torvalds1da177e2005-04-16 15:20:36 -070051#define ACPI_BATTERY_CLASS "battery"
Linus Torvalds1da177e2005-04-16 15:20:36 -070052#define ACPI_BATTERY_DEVICE_NAME "Battery"
Linus Torvalds1da177e2005-04-16 15:20:36 -070053#define ACPI_BATTERY_NOTIFY_STATUS 0x80
54#define ACPI_BATTERY_NOTIFY_INFO 0x81
Linus Torvalds1da177e2005-04-16 15:20:36 -070055
Linus Torvalds1da177e2005-04-16 15:20:36 -070056#define _COMPONENT ACPI_BATTERY_COMPONENT
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +030057
Len Brownf52fd662007-02-12 22:42:12 -050058ACPI_MODULE_NAME("battery");
Linus Torvalds1da177e2005-04-16 15:20:36 -070059
Len Brownf52fd662007-02-12 22:42:12 -050060MODULE_AUTHOR("Paul Diefenbaugh");
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +040061MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
Len Brown7cda93e2007-02-12 23:50:02 -050062MODULE_DESCRIPTION("ACPI Battery Driver");
Linus Torvalds1da177e2005-04-16 15:20:36 -070063MODULE_LICENSE("GPL");
64
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +040065static unsigned int cache_time = 1000;
66module_param(cache_time, uint, 0644);
67MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +030068
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +030069#ifdef CONFIG_ACPI_PROCFS_POWER
Rich Townsend3f86b832006-07-01 11:36:54 -040070extern struct proc_dir_entry *acpi_lock_battery_dir(void);
71extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir);
72
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +040073enum acpi_battery_files {
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +040074 info_tag = 0,
75 state_tag,
76 alarm_tag,
Alexey Starikovskiy78490d82007-05-11 13:18:55 -040077 ACPI_BATTERY_NUMFILES,
78};
79
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040080#endif
81
82static const struct acpi_device_id battery_device_ids[] = {
83 {"PNP0C0A", 0},
84 {"", 0},
85};
86
87MODULE_DEVICE_TABLE(acpi, battery_device_ids);
88
Hector Martinbc76f902009-08-06 15:57:48 -070089/* For buggy DSDTs that report negative 16-bit values for either charging
90 * or discharging current and/or report 0 as 65536 due to bad math.
91 */
92#define QUIRK_SIGNED16_CURRENT 0x0001
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040093
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +040094struct acpi_battery {
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +040095 struct mutex lock;
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -050096#ifdef CONFIG_ACPI_SYSFS_POWER
Alexey Starikovskiyd7380962007-09-26 19:43:04 +040097 struct power_supply bat;
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -050098#endif
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +040099 struct acpi_device *device;
100 unsigned long update_time;
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400101 int rate_now;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400102 int capacity_now;
103 int voltage_now;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400104 int design_capacity;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400105 int full_charge_capacity;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400106 int technology;
107 int design_voltage;
108 int design_capacity_warning;
109 int design_capacity_low;
110 int capacity_granularity_1;
111 int capacity_granularity_2;
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400112 int alarm;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400113 char model_number[32];
114 char serial_number[32];
115 char type[32];
116 char oem_info[32];
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400117 int state;
118 int power_unit;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300119 u8 alarm_present;
Hector Martinbc76f902009-08-06 15:57:48 -0700120 long quirks;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121};
122
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400123#define to_acpi_battery(x) container_of(x, struct acpi_battery, bat);
124
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400125inline int acpi_battery_present(struct acpi_battery *battery)
126{
127 return battery->device->status.battery_present;
128}
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400129
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500130#ifdef CONFIG_ACPI_SYSFS_POWER
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400131static int acpi_battery_technology(struct acpi_battery *battery)
132{
133 if (!strcasecmp("NiCd", battery->type))
134 return POWER_SUPPLY_TECHNOLOGY_NiCd;
135 if (!strcasecmp("NiMH", battery->type))
136 return POWER_SUPPLY_TECHNOLOGY_NiMH;
137 if (!strcasecmp("LION", battery->type))
138 return POWER_SUPPLY_TECHNOLOGY_LION;
Andrey Borzenkovad40e682007-11-10 20:02:49 +0300139 if (!strncasecmp("LI-ION", battery->type, 6))
Alexey Starikovskiy0bde7ee2007-10-28 15:33:10 +0300140 return POWER_SUPPLY_TECHNOLOGY_LION;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400141 if (!strcasecmp("LiP", battery->type))
142 return POWER_SUPPLY_TECHNOLOGY_LIPO;
143 return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
144}
145
Alexey Starikovskiy91044762007-11-13 12:23:06 +0300146static int acpi_battery_get_state(struct acpi_battery *battery);
Alexey Starikovskiyb19073a2007-10-25 17:10:47 -0400147
Richard Hughes56f382a2009-01-25 15:05:50 +0000148static int acpi_battery_is_charged(struct acpi_battery *battery)
149{
150 /* either charging or discharging */
151 if (battery->state != 0)
152 return 0;
153
154 /* battery not reporting charge */
155 if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN ||
156 battery->capacity_now == 0)
157 return 0;
158
159 /* good batteries update full_charge as the batteries degrade */
160 if (battery->full_charge_capacity == battery->capacity_now)
161 return 1;
162
163 /* fallback to using design values for broken batteries */
164 if (battery->design_capacity == battery->capacity_now)
165 return 1;
166
167 /* we don't do any sort of metric based on percentages */
168 return 0;
169}
170
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400171static int acpi_battery_get_property(struct power_supply *psy,
172 enum power_supply_property psp,
173 union power_supply_propval *val)
174{
175 struct acpi_battery *battery = to_acpi_battery(psy);
176
Alexey Starikovskiy91044762007-11-13 12:23:06 +0300177 if (acpi_battery_present(battery)) {
178 /* run battery update only if it is present */
179 acpi_battery_get_state(battery);
180 } else if (psp != POWER_SUPPLY_PROP_PRESENT)
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400181 return -ENODEV;
182 switch (psp) {
183 case POWER_SUPPLY_PROP_STATUS:
184 if (battery->state & 0x01)
185 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
186 else if (battery->state & 0x02)
187 val->intval = POWER_SUPPLY_STATUS_CHARGING;
Richard Hughes56f382a2009-01-25 15:05:50 +0000188 else if (acpi_battery_is_charged(battery))
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400189 val->intval = POWER_SUPPLY_STATUS_FULL;
Roland Dreier4c41d3a2007-11-07 15:09:09 -0800190 else
191 val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400192 break;
193 case POWER_SUPPLY_PROP_PRESENT:
194 val->intval = acpi_battery_present(battery);
195 break;
196 case POWER_SUPPLY_PROP_TECHNOLOGY:
197 val->intval = acpi_battery_technology(battery);
198 break;
199 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
200 val->intval = battery->design_voltage * 1000;
201 break;
202 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
203 val->intval = battery->voltage_now * 1000;
204 break;
205 case POWER_SUPPLY_PROP_CURRENT_NOW:
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400206 case POWER_SUPPLY_PROP_POWER_NOW:
207 val->intval = battery->rate_now * 1000;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400208 break;
209 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
210 case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
211 val->intval = battery->design_capacity * 1000;
212 break;
213 case POWER_SUPPLY_PROP_CHARGE_FULL:
214 case POWER_SUPPLY_PROP_ENERGY_FULL:
215 val->intval = battery->full_charge_capacity * 1000;
216 break;
217 case POWER_SUPPLY_PROP_CHARGE_NOW:
218 case POWER_SUPPLY_PROP_ENERGY_NOW:
219 val->intval = battery->capacity_now * 1000;
220 break;
221 case POWER_SUPPLY_PROP_MODEL_NAME:
222 val->strval = battery->model_number;
223 break;
224 case POWER_SUPPLY_PROP_MANUFACTURER:
225 val->strval = battery->oem_info;
226 break;
maximilian attems7c2670b2008-01-22 18:46:50 +0100227 case POWER_SUPPLY_PROP_SERIAL_NUMBER:
228 val->strval = battery->serial_number;
229 break;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400230 default:
231 return -EINVAL;
232 }
233 return 0;
234}
235
236static enum power_supply_property charge_battery_props[] = {
237 POWER_SUPPLY_PROP_STATUS,
238 POWER_SUPPLY_PROP_PRESENT,
239 POWER_SUPPLY_PROP_TECHNOLOGY,
240 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
241 POWER_SUPPLY_PROP_VOLTAGE_NOW,
242 POWER_SUPPLY_PROP_CURRENT_NOW,
243 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
244 POWER_SUPPLY_PROP_CHARGE_FULL,
245 POWER_SUPPLY_PROP_CHARGE_NOW,
246 POWER_SUPPLY_PROP_MODEL_NAME,
247 POWER_SUPPLY_PROP_MANUFACTURER,
maximilian attems7c2670b2008-01-22 18:46:50 +0100248 POWER_SUPPLY_PROP_SERIAL_NUMBER,
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400249};
250
251static enum power_supply_property energy_battery_props[] = {
252 POWER_SUPPLY_PROP_STATUS,
253 POWER_SUPPLY_PROP_PRESENT,
254 POWER_SUPPLY_PROP_TECHNOLOGY,
255 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
256 POWER_SUPPLY_PROP_VOLTAGE_NOW,
257 POWER_SUPPLY_PROP_CURRENT_NOW,
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400258 POWER_SUPPLY_PROP_POWER_NOW,
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400259 POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
260 POWER_SUPPLY_PROP_ENERGY_FULL,
261 POWER_SUPPLY_PROP_ENERGY_NOW,
262 POWER_SUPPLY_PROP_MODEL_NAME,
263 POWER_SUPPLY_PROP_MANUFACTURER,
maximilian attems7c2670b2008-01-22 18:46:50 +0100264 POWER_SUPPLY_PROP_SERIAL_NUMBER,
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400265};
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500266#endif
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400267
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300268#ifdef CONFIG_ACPI_PROCFS_POWER
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400269inline char *acpi_battery_units(struct acpi_battery *battery)
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400270{
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400271 return (battery->power_unit)?"mA":"mW";
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400272}
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400273#endif
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300274
Linus Torvalds1da177e2005-04-16 15:20:36 -0700275/* --------------------------------------------------------------------------
276 Battery Management
277 -------------------------------------------------------------------------- */
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400278struct acpi_offsets {
279 size_t offset; /* offset inside struct acpi_sbs_battery */
280 u8 mode; /* int or string? */
281};
282
283static struct acpi_offsets state_offsets[] = {
284 {offsetof(struct acpi_battery, state), 0},
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400285 {offsetof(struct acpi_battery, rate_now), 0},
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400286 {offsetof(struct acpi_battery, capacity_now), 0},
287 {offsetof(struct acpi_battery, voltage_now), 0},
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400288};
289
290static struct acpi_offsets info_offsets[] = {
291 {offsetof(struct acpi_battery, power_unit), 0},
292 {offsetof(struct acpi_battery, design_capacity), 0},
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400293 {offsetof(struct acpi_battery, full_charge_capacity), 0},
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400294 {offsetof(struct acpi_battery, technology), 0},
295 {offsetof(struct acpi_battery, design_voltage), 0},
296 {offsetof(struct acpi_battery, design_capacity_warning), 0},
297 {offsetof(struct acpi_battery, design_capacity_low), 0},
298 {offsetof(struct acpi_battery, capacity_granularity_1), 0},
299 {offsetof(struct acpi_battery, capacity_granularity_2), 0},
300 {offsetof(struct acpi_battery, model_number), 1},
301 {offsetof(struct acpi_battery, serial_number), 1},
302 {offsetof(struct acpi_battery, type), 1},
303 {offsetof(struct acpi_battery, oem_info), 1},
304};
305
306static int extract_package(struct acpi_battery *battery,
307 union acpi_object *package,
308 struct acpi_offsets *offsets, int num)
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300309{
Alexey Starikovskiy106449e2007-10-29 23:29:40 +0300310 int i;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400311 union acpi_object *element;
312 if (package->type != ACPI_TYPE_PACKAGE)
313 return -EFAULT;
314 for (i = 0; i < num; ++i) {
315 if (package->package.count <= i)
316 return -EFAULT;
317 element = &package->package.elements[i];
318 if (offsets[i].mode) {
Alexey Starikovskiy106449e2007-10-29 23:29:40 +0300319 u8 *ptr = (u8 *)battery + offsets[i].offset;
320 if (element->type == ACPI_TYPE_STRING ||
321 element->type == ACPI_TYPE_BUFFER)
322 strncpy(ptr, element->string.pointer, 32);
323 else if (element->type == ACPI_TYPE_INTEGER) {
324 strncpy(ptr, (u8 *)&element->integer.value,
325 sizeof(acpi_integer));
326 ptr[sizeof(acpi_integer)] = 0;
Alexey Starikovskiyb8a1bdb2008-03-17 22:37:42 -0400327 } else
328 *ptr = 0; /* don't have value */
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400329 } else {
Alexey Starikovskiyb8a1bdb2008-03-17 22:37:42 -0400330 int *x = (int *)((u8 *)battery + offsets[i].offset);
331 *x = (element->type == ACPI_TYPE_INTEGER) ?
332 element->integer.value : -1;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300333 }
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300334 }
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300335 return 0;
336}
337
338static int acpi_battery_get_status(struct acpi_battery *battery)
339{
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400340 if (acpi_bus_get_status(battery->device)) {
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300341 ACPI_EXCEPTION((AE_INFO, AE_ERROR, "Evaluating _STA"));
342 return -ENODEV;
343 }
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400344 return 0;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300345}
346
347static int acpi_battery_get_info(struct acpi_battery *battery)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348{
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400349 int result = -EFAULT;
Len Brown4be44fc2005-08-05 00:44:28 -0400350 acpi_status status = 0;
351 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300353 if (!acpi_battery_present(battery))
354 return 0;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400355 mutex_lock(&battery->lock);
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400356 status = acpi_evaluate_object(battery->device->handle, "_BIF",
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400357 NULL, &buffer);
358 mutex_unlock(&battery->lock);
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400359
Linus Torvalds1da177e2005-04-16 15:20:36 -0700360 if (ACPI_FAILURE(status)) {
Thomas Renningera6fc6722006-06-26 23:58:43 -0400361 ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BIF"));
Patrick Mocheld550d982006-06-27 00:41:40 -0400362 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700363 }
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400364
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400365 result = extract_package(battery, buffer.pointer,
366 info_offsets, ARRAY_SIZE(info_offsets));
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400367 kfree(buffer.pointer);
Patrick Mocheld550d982006-06-27 00:41:40 -0400368 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700369}
370
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300371static int acpi_battery_get_state(struct acpi_battery *battery)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372{
Len Brown4be44fc2005-08-05 00:44:28 -0400373 int result = 0;
374 acpi_status status = 0;
375 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700376
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300377 if (!acpi_battery_present(battery))
378 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700379
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400380 if (battery->update_time &&
381 time_before(jiffies, battery->update_time +
382 msecs_to_jiffies(cache_time)))
383 return 0;
384
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400385 mutex_lock(&battery->lock);
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400386 status = acpi_evaluate_object(battery->device->handle, "_BST",
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400387 NULL, &buffer);
388 mutex_unlock(&battery->lock);
Len Brown5b31d892007-08-15 00:19:26 -0400389
Linus Torvalds1da177e2005-04-16 15:20:36 -0700390 if (ACPI_FAILURE(status)) {
Thomas Renningera6fc6722006-06-26 23:58:43 -0400391 ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BST"));
Patrick Mocheld550d982006-06-27 00:41:40 -0400392 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700393 }
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400394
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400395 result = extract_package(battery, buffer.pointer,
396 state_offsets, ARRAY_SIZE(state_offsets));
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400397 battery->update_time = jiffies;
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400398 kfree(buffer.pointer);
Hector Martinbc76f902009-08-06 15:57:48 -0700399
400 if ((battery->quirks & QUIRK_SIGNED16_CURRENT) &&
401 battery->rate_now != -1)
402 battery->rate_now = abs((s16)battery->rate_now);
403
Patrick Mocheld550d982006-06-27 00:41:40 -0400404 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700405}
406
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400407static int acpi_battery_set_alarm(struct acpi_battery *battery)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700408{
Len Brown4be44fc2005-08-05 00:44:28 -0400409 acpi_status status = 0;
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400410 union acpi_object arg0 = { .type = ACPI_TYPE_INTEGER };
Len Brown4be44fc2005-08-05 00:44:28 -0400411 struct acpi_object_list arg_list = { 1, &arg0 };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400413 if (!acpi_battery_present(battery)|| !battery->alarm_present)
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300414 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700415
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400416 arg0.integer.value = battery->alarm;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700417
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400418 mutex_lock(&battery->lock);
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400419 status = acpi_evaluate_object(battery->device->handle, "_BTP",
420 &arg_list, NULL);
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400421 mutex_unlock(&battery->lock);
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400422
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400424 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700425
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400426 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Alarm set to %d\n", battery->alarm));
Patrick Mocheld550d982006-06-27 00:41:40 -0400427 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700428}
429
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300430static int acpi_battery_init_alarm(struct acpi_battery *battery)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700431{
Len Brown4be44fc2005-08-05 00:44:28 -0400432 acpi_status status = AE_OK;
433 acpi_handle handle = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700434
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300435 /* See if alarms are supported, and if so, set default */
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400436 status = acpi_get_handle(battery->device->handle, "_BTP", &handle);
437 if (ACPI_FAILURE(status)) {
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400438 battery->alarm_present = 0;
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400439 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700440 }
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400441 battery->alarm_present = 1;
442 if (!battery->alarm)
443 battery->alarm = battery->design_capacity_warning;
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400444 return acpi_battery_set_alarm(battery);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700445}
446
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500447#ifdef CONFIG_ACPI_SYSFS_POWER
Andrey Borzenkov508df922007-10-28 12:50:09 +0300448static ssize_t acpi_battery_alarm_show(struct device *dev,
449 struct device_attribute *attr,
450 char *buf)
451{
452 struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev));
453 return sprintf(buf, "%d\n", battery->alarm * 1000);
454}
455
456static ssize_t acpi_battery_alarm_store(struct device *dev,
457 struct device_attribute *attr,
458 const char *buf, size_t count)
459{
460 unsigned long x;
461 struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev));
462 if (sscanf(buf, "%ld\n", &x) == 1)
463 battery->alarm = x/1000;
464 if (acpi_battery_present(battery))
465 acpi_battery_set_alarm(battery);
466 return count;
467}
468
469static struct device_attribute alarm_attr = {
Parag Warudkar01e8ef12008-10-18 20:28:50 -0700470 .attr = {.name = "alarm", .mode = 0644},
Andrey Borzenkov508df922007-10-28 12:50:09 +0300471 .show = acpi_battery_alarm_show,
472 .store = acpi_battery_alarm_store,
473};
474
475static int sysfs_add_battery(struct acpi_battery *battery)
476{
477 int result;
478
Andrey Borzenkov508df922007-10-28 12:50:09 +0300479 if (battery->power_unit) {
480 battery->bat.properties = charge_battery_props;
481 battery->bat.num_properties =
482 ARRAY_SIZE(charge_battery_props);
483 } else {
484 battery->bat.properties = energy_battery_props;
485 battery->bat.num_properties =
486 ARRAY_SIZE(energy_battery_props);
487 }
488
489 battery->bat.name = acpi_device_bid(battery->device);
490 battery->bat.type = POWER_SUPPLY_TYPE_BATTERY;
491 battery->bat.get_property = acpi_battery_get_property;
492
493 result = power_supply_register(&battery->device->dev, &battery->bat);
494 if (result)
495 return result;
496 return device_create_file(battery->bat.dev, &alarm_attr);
497}
498
499static void sysfs_remove_battery(struct acpi_battery *battery)
500{
501 if (!battery->bat.dev)
502 return;
503 device_remove_file(battery->bat.dev, &alarm_attr);
504 power_supply_unregister(&battery->bat);
Alexey Starikovskiy91044762007-11-13 12:23:06 +0300505 battery->bat.dev = NULL;
Andrey Borzenkov508df922007-10-28 12:50:09 +0300506}
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500507#endif
Andrey Borzenkov508df922007-10-28 12:50:09 +0300508
Hector Martinbc76f902009-08-06 15:57:48 -0700509static void acpi_battery_quirks(struct acpi_battery *battery)
510{
511 battery->quirks = 0;
512 if (dmi_name_in_vendors("Acer") && battery->power_unit) {
513 battery->quirks |= QUIRK_SIGNED16_CURRENT;
514 }
515}
516
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400517static int acpi_battery_update(struct acpi_battery *battery)
Vladimir Lebedev4bd35cd2007-02-10 01:43:48 -0500518{
Alexey Starikovskiy50b17852008-12-23 02:44:54 +0300519 int result, old_present = acpi_battery_present(battery);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500520 result = acpi_battery_get_status(battery);
Andrey Borzenkov508df922007-10-28 12:50:09 +0300521 if (result)
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300522 return result;
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500523#ifdef CONFIG_ACPI_SYSFS_POWER
Andrey Borzenkov508df922007-10-28 12:50:09 +0300524 if (!acpi_battery_present(battery)) {
525 sysfs_remove_battery(battery);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500526 battery->update_time = 0;
Andrey Borzenkov508df922007-10-28 12:50:09 +0300527 return 0;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300528 }
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500529#endif
Alexey Starikovskiy50b17852008-12-23 02:44:54 +0300530 if (!battery->update_time ||
531 old_present != acpi_battery_present(battery)) {
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500532 result = acpi_battery_get_info(battery);
533 if (result)
534 return result;
Hector Martinbc76f902009-08-06 15:57:48 -0700535 acpi_battery_quirks(battery);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500536 acpi_battery_init_alarm(battery);
537 }
538#ifdef CONFIG_ACPI_SYSFS_POWER
Andrey Borzenkov508df922007-10-28 12:50:09 +0300539 if (!battery->bat.dev)
540 sysfs_add_battery(battery);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500541#endif
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400542 return acpi_battery_get_state(battery);
Vladimir Lebedev4bd35cd2007-02-10 01:43:48 -0500543}
544
Linus Torvalds1da177e2005-04-16 15:20:36 -0700545/* --------------------------------------------------------------------------
546 FS Interface (/proc)
547 -------------------------------------------------------------------------- */
548
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300549#ifdef CONFIG_ACPI_PROCFS_POWER
Len Brown4be44fc2005-08-05 00:44:28 -0400550static struct proc_dir_entry *acpi_battery_dir;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300551
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400552static int acpi_battery_print_info(struct seq_file *seq, int result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700553{
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200554 struct acpi_battery *battery = seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700555
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300556 if (result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557 goto end;
558
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400559 seq_printf(seq, "present: %s\n",
560 acpi_battery_present(battery)?"yes":"no");
561 if (!acpi_battery_present(battery))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 goto end;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400563 if (battery->design_capacity == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564 seq_printf(seq, "design capacity: unknown\n");
565 else
566 seq_printf(seq, "design capacity: %d %sh\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400567 battery->design_capacity,
568 acpi_battery_units(battery));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700569
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400570 if (battery->full_charge_capacity == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700571 seq_printf(seq, "last full capacity: unknown\n");
572 else
573 seq_printf(seq, "last full capacity: %d %sh\n",
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400574 battery->full_charge_capacity,
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400575 acpi_battery_units(battery));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700576
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400577 seq_printf(seq, "battery technology: %srechargeable\n",
578 (!battery->technology)?"non-":"");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700579
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400580 if (battery->design_voltage == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700581 seq_printf(seq, "design voltage: unknown\n");
582 else
583 seq_printf(seq, "design voltage: %d mV\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400584 battery->design_voltage);
Len Brown4be44fc2005-08-05 00:44:28 -0400585 seq_printf(seq, "design capacity warning: %d %sh\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400586 battery->design_capacity_warning,
587 acpi_battery_units(battery));
Len Brown4be44fc2005-08-05 00:44:28 -0400588 seq_printf(seq, "design capacity low: %d %sh\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400589 battery->design_capacity_low,
590 acpi_battery_units(battery));
Len Brown4be44fc2005-08-05 00:44:28 -0400591 seq_printf(seq, "capacity granularity 1: %d %sh\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400592 battery->capacity_granularity_1,
593 acpi_battery_units(battery));
Len Brown4be44fc2005-08-05 00:44:28 -0400594 seq_printf(seq, "capacity granularity 2: %d %sh\n",
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400595 battery->capacity_granularity_2,
596 acpi_battery_units(battery));
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400597 seq_printf(seq, "model number: %s\n", battery->model_number);
598 seq_printf(seq, "serial number: %s\n", battery->serial_number);
599 seq_printf(seq, "battery type: %s\n", battery->type);
600 seq_printf(seq, "OEM info: %s\n", battery->oem_info);
Len Brown4be44fc2005-08-05 00:44:28 -0400601 end:
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300602 if (result)
603 seq_printf(seq, "ERROR: Unable to read battery info\n");
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300604 return result;
605}
606
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400607static int acpi_battery_print_state(struct seq_file *seq, int result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700608{
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200609 struct acpi_battery *battery = seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700610
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300611 if (result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700612 goto end;
613
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400614 seq_printf(seq, "present: %s\n",
615 acpi_battery_present(battery)?"yes":"no");
616 if (!acpi_battery_present(battery))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700617 goto end;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700618
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400619 seq_printf(seq, "capacity state: %s\n",
620 (battery->state & 0x04)?"critical":"ok");
621 if ((battery->state & 0x01) && (battery->state & 0x02))
Len Brown4be44fc2005-08-05 00:44:28 -0400622 seq_printf(seq,
623 "charging state: charging/discharging\n");
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400624 else if (battery->state & 0x01)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700625 seq_printf(seq, "charging state: discharging\n");
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400626 else if (battery->state & 0x02)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627 seq_printf(seq, "charging state: charging\n");
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400628 else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700629 seq_printf(seq, "charging state: charged\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700630
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400631 if (battery->rate_now == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700632 seq_printf(seq, "present rate: unknown\n");
633 else
634 seq_printf(seq, "present rate: %d %s\n",
Alexey Starikovskiy7faa1442009-03-27 22:23:52 -0400635 battery->rate_now, acpi_battery_units(battery));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700636
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400637 if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700638 seq_printf(seq, "remaining capacity: unknown\n");
639 else
640 seq_printf(seq, "remaining capacity: %d %sh\n",
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400641 battery->capacity_now, acpi_battery_units(battery));
642 if (battery->voltage_now == ACPI_BATTERY_VALUE_UNKNOWN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700643 seq_printf(seq, "present voltage: unknown\n");
644 else
645 seq_printf(seq, "present voltage: %d mV\n",
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400646 battery->voltage_now);
Len Brown4be44fc2005-08-05 00:44:28 -0400647 end:
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400648 if (result)
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300649 seq_printf(seq, "ERROR: Unable to read battery state\n");
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300650
651 return result;
652}
653
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400654static int acpi_battery_print_alarm(struct seq_file *seq, int result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700655{
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200656 struct acpi_battery *battery = seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700657
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300658 if (result)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700659 goto end;
660
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300661 if (!acpi_battery_present(battery)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700662 seq_printf(seq, "present: no\n");
663 goto end;
664 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700665 seq_printf(seq, "alarm: ");
666 if (!battery->alarm)
667 seq_printf(seq, "unsupported\n");
668 else
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400669 seq_printf(seq, "%u %sh\n", battery->alarm,
670 acpi_battery_units(battery));
Len Brown4be44fc2005-08-05 00:44:28 -0400671 end:
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300672 if (result)
673 seq_printf(seq, "ERROR: Unable to read battery alarm\n");
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300674 return result;
675}
676
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400677static ssize_t acpi_battery_write_alarm(struct file *file,
678 const char __user * buffer,
679 size_t count, loff_t * ppos)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700680{
Len Brown4be44fc2005-08-05 00:44:28 -0400681 int result = 0;
682 char alarm_string[12] = { '\0' };
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200683 struct seq_file *m = file->private_data;
684 struct acpi_battery *battery = m->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700685
Linus Torvalds1da177e2005-04-16 15:20:36 -0700686 if (!battery || (count > sizeof(alarm_string) - 1))
Patrick Mocheld550d982006-06-27 00:41:40 -0400687 return -EINVAL;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300688 if (!acpi_battery_present(battery)) {
689 result = -ENODEV;
690 goto end;
691 }
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300692 if (copy_from_user(alarm_string, buffer, count)) {
693 result = -EFAULT;
694 goto end;
695 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700696 alarm_string[count] = '\0';
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400697 battery->alarm = simple_strtol(alarm_string, NULL, 0);
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400698 result = acpi_battery_set_alarm(battery);
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300699 end:
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300700 if (!result)
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400701 return count;
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300702 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700703}
704
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400705typedef int(*print_func)(struct seq_file *seq, int result);
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400706
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400707static print_func acpi_print_funcs[ACPI_BATTERY_NUMFILES] = {
708 acpi_battery_print_info,
709 acpi_battery_print_state,
710 acpi_battery_print_alarm,
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400711};
712
713static int acpi_battery_read(int fid, struct seq_file *seq)
714{
715 struct acpi_battery *battery = seq->private;
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400716 int result = acpi_battery_update(battery);
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400717 return acpi_print_funcs[fid](seq, result);
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400718}
719
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400720#define DECLARE_FILE_FUNCTIONS(_name) \
721static int acpi_battery_read_##_name(struct seq_file *seq, void *offset) \
722{ \
723 return acpi_battery_read(_name##_tag, seq); \
724} \
725static int acpi_battery_##_name##_open_fs(struct inode *inode, struct file *file) \
726{ \
727 return single_open(file, acpi_battery_read_##_name, PDE(inode)->data); \
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400728}
729
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400730DECLARE_FILE_FUNCTIONS(info);
731DECLARE_FILE_FUNCTIONS(state);
732DECLARE_FILE_FUNCTIONS(alarm);
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400733
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400734#undef DECLARE_FILE_FUNCTIONS
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400735
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400736#define FILE_DESCRIPTION_RO(_name) \
737 { \
738 .name = __stringify(_name), \
739 .mode = S_IRUGO, \
740 .ops = { \
741 .open = acpi_battery_##_name##_open_fs, \
742 .read = seq_read, \
743 .llseek = seq_lseek, \
744 .release = single_release, \
745 .owner = THIS_MODULE, \
746 }, \
747 }
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400748
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400749#define FILE_DESCRIPTION_RW(_name) \
750 { \
751 .name = __stringify(_name), \
752 .mode = S_IFREG | S_IRUGO | S_IWUSR, \
753 .ops = { \
754 .open = acpi_battery_##_name##_open_fs, \
755 .read = seq_read, \
756 .llseek = seq_lseek, \
757 .write = acpi_battery_write_##_name, \
758 .release = single_release, \
759 .owner = THIS_MODULE, \
760 }, \
761 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700762
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400763static struct battery_file {
764 struct file_operations ops;
765 mode_t mode;
Jan Engelhardt070d8eb2009-01-12 00:07:55 +0100766 const char *name;
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400767} acpi_battery_file[] = {
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400768 FILE_DESCRIPTION_RO(info),
769 FILE_DESCRIPTION_RO(state),
770 FILE_DESCRIPTION_RW(alarm),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700771};
772
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400773#undef FILE_DESCRIPTION_RO
774#undef FILE_DESCRIPTION_RW
775
Len Brown4be44fc2005-08-05 00:44:28 -0400776static int acpi_battery_add_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700777{
Len Brown4be44fc2005-08-05 00:44:28 -0400778 struct proc_dir_entry *entry = NULL;
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400779 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700780
Linus Torvalds1da177e2005-04-16 15:20:36 -0700781 if (!acpi_device_dir(device)) {
782 acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
Len Brown4be44fc2005-08-05 00:44:28 -0400783 acpi_battery_dir);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700784 if (!acpi_device_dir(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400785 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700786 }
787
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400788 for (i = 0; i < ACPI_BATTERY_NUMFILES; ++i) {
Denis V. Lunevcf7acfa2008-04-29 01:02:27 -0700789 entry = proc_create_data(acpi_battery_file[i].name,
790 acpi_battery_file[i].mode,
791 acpi_device_dir(device),
792 &acpi_battery_file[i].ops,
793 acpi_driver_data(device));
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400794 if (!entry)
795 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700796 }
Patrick Mocheld550d982006-06-27 00:41:40 -0400797 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700798}
799
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400800static void acpi_battery_remove_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700801{
Alexey Starikovskiy78490d82007-05-11 13:18:55 -0400802 int i;
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400803 if (!acpi_device_dir(device))
804 return;
805 for (i = 0; i < ACPI_BATTERY_NUMFILES; ++i)
806 remove_proc_entry(acpi_battery_file[i].name,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700807 acpi_device_dir(device));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700808
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400809 remove_proc_entry(acpi_device_bid(device), acpi_battery_dir);
810 acpi_device_dir(device) = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700811}
812
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400813#endif
Alexey Starikovskiy3e58ea02007-09-26 19:43:11 +0400814
Linus Torvalds1da177e2005-04-16 15:20:36 -0700815/* --------------------------------------------------------------------------
816 Driver Interface
817 -------------------------------------------------------------------------- */
818
Bjorn Helgaasd9406692009-04-30 09:35:47 -0600819static void acpi_battery_notify(struct acpi_device *device, u32 event)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700820{
Bjorn Helgaasd9406692009-04-30 09:35:47 -0600821 struct acpi_battery *battery = acpi_driver_data(device);
822
Linus Torvalds1da177e2005-04-16 15:20:36 -0700823 if (!battery)
Patrick Mocheld550d982006-06-27 00:41:40 -0400824 return;
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400825 acpi_battery_update(battery);
826 acpi_bus_generate_proc_event(device, event,
827 acpi_battery_present(battery));
828 acpi_bus_generate_netlink_event(device->pnp.device_class,
Kay Sievers07944692008-10-30 01:18:59 +0100829 dev_name(&device->dev), event,
Vladimir Lebedev9ea7d572007-02-20 15:48:06 +0300830 acpi_battery_present(battery));
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500831#ifdef CONFIG_ACPI_SYSFS_POWER
Andrey Borzenkov508df922007-10-28 12:50:09 +0300832 /* acpi_batter_update could remove power_supply object */
833 if (battery->bat.dev)
834 kobject_uevent(&battery->bat.dev->kobj, KOBJ_CHANGE);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500835#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700836}
837
Len Brown4be44fc2005-08-05 00:44:28 -0400838static int acpi_battery_add(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700839{
Len Brown4be44fc2005-08-05 00:44:28 -0400840 int result = 0;
Len Brown4be44fc2005-08-05 00:44:28 -0400841 struct acpi_battery *battery = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700842 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400843 return -EINVAL;
Burman Yan36bcbec2006-12-19 12:56:11 -0800844 battery = kzalloc(sizeof(struct acpi_battery), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700845 if (!battery)
Patrick Mocheld550d982006-06-27 00:41:40 -0400846 return -ENOMEM;
Patrick Mochel145def82006-05-19 16:54:39 -0400847 battery->device = device;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700848 strcpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME);
849 strcpy(acpi_device_class(device), ACPI_BATTERY_CLASS);
Pavel Machekdb89b4f2008-09-22 14:37:34 -0700850 device->driver_data = battery;
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400851 mutex_init(&battery->lock);
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400852 acpi_battery_update(battery);
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300853#ifdef CONFIG_ACPI_PROCFS_POWER
Linus Torvalds1da177e2005-04-16 15:20:36 -0700854 result = acpi_battery_add_fs(device);
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400855#endif
Len Brown586caae2009-06-18 00:38:27 -0400856 if (!result) {
857 printk(KERN_INFO PREFIX "%s Slot [%s] (battery %s)\n",
858 ACPI_BATTERY_DEVICE_NAME, acpi_device_bid(device),
859 device->status.battery_present ? "present" : "absent");
860 } else {
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300861#ifdef CONFIG_ACPI_PROCFS_POWER
Linus Torvalds1da177e2005-04-16 15:20:36 -0700862 acpi_battery_remove_fs(device);
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400863#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700864 kfree(battery);
865 }
Patrick Mocheld550d982006-06-27 00:41:40 -0400866 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700867}
868
Len Brown4be44fc2005-08-05 00:44:28 -0400869static int acpi_battery_remove(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700870{
Len Brown4be44fc2005-08-05 00:44:28 -0400871 struct acpi_battery *battery = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700872
Linus Torvalds1da177e2005-04-16 15:20:36 -0700873 if (!device || !acpi_driver_data(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400874 return -EINVAL;
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200875 battery = acpi_driver_data(device);
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300876#ifdef CONFIG_ACPI_PROCFS_POWER
Linus Torvalds1da177e2005-04-16 15:20:36 -0700877 acpi_battery_remove_fs(device);
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400878#endif
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500879#ifdef CONFIG_ACPI_SYSFS_POWER
Andrey Borzenkov508df922007-10-28 12:50:09 +0300880 sysfs_remove_battery(battery);
Alexey Starikovskiy97749cd2008-01-01 14:27:24 -0500881#endif
Alexey Starikovskiy038fdea2007-09-26 19:42:46 +0400882 mutex_destroy(&battery->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700883 kfree(battery);
Patrick Mocheld550d982006-06-27 00:41:40 -0400884 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700885}
886
Jiri Kosina34c44152006-10-10 14:20:41 -0700887/* this is needed to learn about changes made in suspended state */
Patrick Mochel5d9464a2006-12-07 20:56:27 +0800888static int acpi_battery_resume(struct acpi_device *device)
Jiri Kosina34c44152006-10-10 14:20:41 -0700889{
890 struct acpi_battery *battery;
Jiri Kosina34c44152006-10-10 14:20:41 -0700891 if (!device)
892 return -EINVAL;
Alexey Starikovskiyf1d46612007-09-26 19:42:52 +0400893 battery = acpi_driver_data(device);
894 battery->update_time = 0;
Andrey Borzenkov508df922007-10-28 12:50:09 +0300895 acpi_battery_update(battery);
Vladimir Lebedevb6ce4082007-02-20 15:48:06 +0300896 return 0;
Jiri Kosina34c44152006-10-10 14:20:41 -0700897}
898
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400899static struct acpi_driver acpi_battery_driver = {
900 .name = "battery",
901 .class = ACPI_BATTERY_CLASS,
902 .ids = battery_device_ids,
Bjorn Helgaasd9406692009-04-30 09:35:47 -0600903 .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400904 .ops = {
905 .add = acpi_battery_add,
906 .resume = acpi_battery_resume,
907 .remove = acpi_battery_remove,
Bjorn Helgaasd9406692009-04-30 09:35:47 -0600908 .notify = acpi_battery_notify,
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400909 },
910};
911
Linus Torvaldsb0cbc862009-04-11 12:45:20 -0700912static void __init acpi_battery_init_async(void *unused, async_cookie_t cookie)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700913{
Pavel Machek4d8316d2006-08-14 22:37:22 -0700914 if (acpi_disabled)
Arjan van de Ven0f66af52009-01-10 14:19:05 -0500915 return;
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300916#ifdef CONFIG_ACPI_PROCFS_POWER
Rich Townsend3f86b832006-07-01 11:36:54 -0400917 acpi_battery_dir = acpi_lock_battery_dir();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700918 if (!acpi_battery_dir)
Arjan van de Ven0f66af52009-01-10 14:19:05 -0500919 return;
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400920#endif
Alexey Starikovskiyaa650bb2007-09-26 19:42:58 +0400921 if (acpi_bus_register_driver(&acpi_battery_driver) < 0) {
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300922#ifdef CONFIG_ACPI_PROCFS_POWER
Rich Townsend3f86b832006-07-01 11:36:54 -0400923 acpi_unlock_battery_dir(acpi_battery_dir);
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400924#endif
Arjan van de Ven0f66af52009-01-10 14:19:05 -0500925 return;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700926 }
Arjan van de Ven0f66af52009-01-10 14:19:05 -0500927 return;
928}
929
930static int __init acpi_battery_init(void)
931{
932 async_schedule(acpi_battery_init_async, NULL);
Patrick Mocheld550d982006-06-27 00:41:40 -0400933 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700934}
935
Len Brown4be44fc2005-08-05 00:44:28 -0400936static void __exit acpi_battery_exit(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700937{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700938 acpi_bus_unregister_driver(&acpi_battery_driver);
Alexey Starikovskiyfdcedbb2007-11-19 16:33:45 +0300939#ifdef CONFIG_ACPI_PROCFS_POWER
Rich Townsend3f86b832006-07-01 11:36:54 -0400940 acpi_unlock_battery_dir(acpi_battery_dir);
Alexey Starikovskiyd7380962007-09-26 19:43:04 +0400941#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700942}
943
Linus Torvalds1da177e2005-04-16 15:20:36 -0700944module_init(acpi_battery_init);
945module_exit(acpi_battery_exit);