]>
Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
aa650bbd | 2 | * battery.c - ACPI Battery Driver (Revision: 2.0) |
1da177e4 | 3 | * |
aa650bbd AS |
4 | * Copyright (C) 2007 Alexey Starikovskiy <astarikovskiy@suse.de> |
5 | * Copyright (C) 2004-2007 Vladimir Lebedev <vladimir.p.lebedev@intel.com> | |
1da177e4 LT |
6 | * 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> | |
f1d4661a | 32 | #include <linux/jiffies.h> |
1da177e4 LT |
33 | #include <linux/proc_fs.h> |
34 | #include <linux/seq_file.h> | |
35 | #include <asm/uaccess.h> | |
36 | ||
37 | #include <acpi/acpi_bus.h> | |
38 | #include <acpi/acpi_drivers.h> | |
39 | ||
1da177e4 LT |
40 | #define ACPI_BATTERY_VALUE_UNKNOWN 0xFFFFFFFF |
41 | ||
1da177e4 LT |
42 | #define ACPI_BATTERY_COMPONENT 0x00040000 |
43 | #define ACPI_BATTERY_CLASS "battery" | |
1da177e4 | 44 | #define ACPI_BATTERY_DEVICE_NAME "Battery" |
1da177e4 LT |
45 | #define ACPI_BATTERY_NOTIFY_STATUS 0x80 |
46 | #define ACPI_BATTERY_NOTIFY_INFO 0x81 | |
1da177e4 | 47 | |
1da177e4 | 48 | #define _COMPONENT ACPI_BATTERY_COMPONENT |
b6ce4083 | 49 | |
f52fd66d | 50 | ACPI_MODULE_NAME("battery"); |
1da177e4 | 51 | |
f52fd66d | 52 | MODULE_AUTHOR("Paul Diefenbaugh"); |
aa650bbd | 53 | MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>"); |
7cda93e0 | 54 | MODULE_DESCRIPTION("ACPI Battery Driver"); |
1da177e4 LT |
55 | MODULE_LICENSE("GPL"); |
56 | ||
f1d4661a AS |
57 | static unsigned int cache_time = 1000; |
58 | module_param(cache_time, uint, 0644); | |
59 | MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); | |
b6ce4083 | 60 | |
3f86b832 RT |
61 | extern struct proc_dir_entry *acpi_lock_battery_dir(void); |
62 | extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); | |
63 | ||
1ba90e3a TR |
64 | static const struct acpi_device_id battery_device_ids[] = { |
65 | {"PNP0C0A", 0}, | |
66 | {"", 0}, | |
67 | }; | |
1ba90e3a | 68 | |
aa650bbd | 69 | MODULE_DEVICE_TABLE(acpi, battery_device_ids); |
1da177e4 | 70 | |
038fdea2 | 71 | enum acpi_battery_files { |
aa650bbd AS |
72 | info_tag = 0, |
73 | state_tag, | |
74 | alarm_tag, | |
78490d82 AS |
75 | ACPI_BATTERY_NUMFILES, |
76 | }; | |
77 | ||
1da177e4 | 78 | struct acpi_battery { |
038fdea2 | 79 | struct mutex lock; |
f1d4661a AS |
80 | struct acpi_device *device; |
81 | unsigned long update_time; | |
038fdea2 AS |
82 | int present_rate; |
83 | int remaining_capacity; | |
84 | int present_voltage; | |
038fdea2 AS |
85 | int design_capacity; |
86 | int last_full_capacity; | |
87 | int technology; | |
88 | int design_voltage; | |
89 | int design_capacity_warning; | |
90 | int design_capacity_low; | |
91 | int capacity_granularity_1; | |
92 | int capacity_granularity_2; | |
f1d4661a | 93 | int alarm; |
038fdea2 AS |
94 | char model_number[32]; |
95 | char serial_number[32]; | |
96 | char type[32]; | |
97 | char oem_info[32]; | |
f1d4661a AS |
98 | int state; |
99 | int power_unit; | |
038fdea2 | 100 | u8 alarm_present; |
1da177e4 LT |
101 | }; |
102 | ||
78490d82 | 103 | inline int acpi_battery_present(struct acpi_battery *battery) |
b6ce4083 | 104 | { |
78490d82 AS |
105 | return battery->device->status.battery_present; |
106 | } | |
038fdea2 | 107 | |
f1d4661a | 108 | inline char *acpi_battery_units(struct acpi_battery *battery) |
78490d82 | 109 | { |
f1d4661a | 110 | return (battery->power_unit)?"mA":"mW"; |
b6ce4083 VL |
111 | } |
112 | ||
78490d82 AS |
113 | /* -------------------------------------------------------------------------- |
114 | Battery Management | |
115 | -------------------------------------------------------------------------- */ | |
038fdea2 AS |
116 | struct acpi_offsets { |
117 | size_t offset; /* offset inside struct acpi_sbs_battery */ | |
118 | u8 mode; /* int or string? */ | |
119 | }; | |
b6ce4083 | 120 | |
038fdea2 AS |
121 | static struct acpi_offsets state_offsets[] = { |
122 | {offsetof(struct acpi_battery, state), 0}, | |
123 | {offsetof(struct acpi_battery, present_rate), 0}, | |
124 | {offsetof(struct acpi_battery, remaining_capacity), 0}, | |
125 | {offsetof(struct acpi_battery, present_voltage), 0}, | |
126 | }; | |
b6ce4083 | 127 | |
038fdea2 AS |
128 | static struct acpi_offsets info_offsets[] = { |
129 | {offsetof(struct acpi_battery, power_unit), 0}, | |
130 | {offsetof(struct acpi_battery, design_capacity), 0}, | |
131 | {offsetof(struct acpi_battery, last_full_capacity), 0}, | |
132 | {offsetof(struct acpi_battery, technology), 0}, | |
133 | {offsetof(struct acpi_battery, design_voltage), 0}, | |
134 | {offsetof(struct acpi_battery, design_capacity_warning), 0}, | |
135 | {offsetof(struct acpi_battery, design_capacity_low), 0}, | |
136 | {offsetof(struct acpi_battery, capacity_granularity_1), 0}, | |
137 | {offsetof(struct acpi_battery, capacity_granularity_2), 0}, | |
138 | {offsetof(struct acpi_battery, model_number), 1}, | |
139 | {offsetof(struct acpi_battery, serial_number), 1}, | |
140 | {offsetof(struct acpi_battery, type), 1}, | |
141 | {offsetof(struct acpi_battery, oem_info), 1}, | |
142 | }; | |
b6ce4083 | 143 | |
038fdea2 AS |
144 | static int extract_package(struct acpi_battery *battery, |
145 | union acpi_object *package, | |
146 | struct acpi_offsets *offsets, int num) | |
147 | { | |
148 | int i, *x; | |
149 | union acpi_object *element; | |
150 | if (package->type != ACPI_TYPE_PACKAGE) | |
151 | return -EFAULT; | |
152 | for (i = 0; i < num; ++i) { | |
153 | if (package->package.count <= i) | |
154 | return -EFAULT; | |
155 | element = &package->package.elements[i]; | |
156 | if (offsets[i].mode) { | |
157 | if (element->type != ACPI_TYPE_STRING && | |
158 | element->type != ACPI_TYPE_BUFFER) | |
159 | return -EFAULT; | |
160 | strncpy((u8 *)battery + offsets[i].offset, | |
161 | element->string.pointer, 32); | |
162 | } else { | |
163 | if (element->type != ACPI_TYPE_INTEGER) | |
164 | return -EFAULT; | |
165 | x = (int *)((u8 *)battery + offsets[i].offset); | |
166 | *x = element->integer.value; | |
167 | } | |
b6ce4083 | 168 | } |
b6ce4083 VL |
169 | return 0; |
170 | } | |
171 | ||
172 | static int acpi_battery_get_status(struct acpi_battery *battery) | |
173 | { | |
aa650bbd | 174 | if (acpi_bus_get_status(battery->device)) { |
b6ce4083 VL |
175 | ACPI_EXCEPTION((AE_INFO, AE_ERROR, "Evaluating _STA")); |
176 | return -ENODEV; | |
177 | } | |
aa650bbd | 178 | return 0; |
b6ce4083 VL |
179 | } |
180 | ||
181 | static int acpi_battery_get_info(struct acpi_battery *battery) | |
1da177e4 | 182 | { |
aa650bbd | 183 | int result = -EFAULT; |
4be44fcd LB |
184 | acpi_status status = 0; |
185 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
1da177e4 | 186 | |
b6ce4083 VL |
187 | if (!acpi_battery_present(battery)) |
188 | return 0; | |
038fdea2 | 189 | mutex_lock(&battery->lock); |
f1d4661a | 190 | status = acpi_evaluate_object(battery->device->handle, "_BIF", |
038fdea2 AS |
191 | NULL, &buffer); |
192 | mutex_unlock(&battery->lock); | |
aa650bbd | 193 | |
1da177e4 | 194 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 195 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BIF")); |
d550d98d | 196 | return -ENODEV; |
1da177e4 | 197 | } |
aa650bbd | 198 | |
038fdea2 AS |
199 | result = extract_package(battery, buffer.pointer, |
200 | info_offsets, ARRAY_SIZE(info_offsets)); | |
78490d82 | 201 | kfree(buffer.pointer); |
d550d98d | 202 | return result; |
1da177e4 LT |
203 | } |
204 | ||
b6ce4083 | 205 | static int acpi_battery_get_state(struct acpi_battery *battery) |
1da177e4 | 206 | { |
4be44fcd LB |
207 | int result = 0; |
208 | acpi_status status = 0; | |
209 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
1da177e4 | 210 | |
b6ce4083 VL |
211 | if (!acpi_battery_present(battery)) |
212 | return 0; | |
1da177e4 | 213 | |
f1d4661a AS |
214 | if (battery->update_time && |
215 | time_before(jiffies, battery->update_time + | |
216 | msecs_to_jiffies(cache_time))) | |
217 | return 0; | |
218 | ||
038fdea2 | 219 | mutex_lock(&battery->lock); |
f1d4661a | 220 | status = acpi_evaluate_object(battery->device->handle, "_BST", |
038fdea2 AS |
221 | NULL, &buffer); |
222 | mutex_unlock(&battery->lock); | |
5b31d895 | 223 | |
1da177e4 | 224 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 225 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BST")); |
d550d98d | 226 | return -ENODEV; |
1da177e4 | 227 | } |
aa650bbd | 228 | |
038fdea2 AS |
229 | result = extract_package(battery, buffer.pointer, |
230 | state_offsets, ARRAY_SIZE(state_offsets)); | |
f1d4661a | 231 | battery->update_time = jiffies; |
78490d82 | 232 | kfree(buffer.pointer); |
b6ce4083 VL |
233 | return result; |
234 | } | |
1da177e4 | 235 | |
aa650bbd | 236 | static int acpi_battery_set_alarm(struct acpi_battery *battery) |
1da177e4 | 237 | { |
4be44fcd | 238 | acpi_status status = 0; |
aa650bbd | 239 | union acpi_object arg0 = { .type = ACPI_TYPE_INTEGER }; |
4be44fcd | 240 | struct acpi_object_list arg_list = { 1, &arg0 }; |
1da177e4 | 241 | |
aa650bbd | 242 | if (!acpi_battery_present(battery)|| !battery->alarm_present) |
d550d98d | 243 | return -ENODEV; |
1da177e4 | 244 | |
aa650bbd | 245 | arg0.integer.value = battery->alarm; |
1da177e4 | 246 | |
038fdea2 | 247 | mutex_lock(&battery->lock); |
f1d4661a AS |
248 | status = acpi_evaluate_object(battery->device->handle, "_BTP", |
249 | &arg_list, NULL); | |
038fdea2 | 250 | mutex_unlock(&battery->lock); |
aa650bbd | 251 | |
1da177e4 | 252 | if (ACPI_FAILURE(status)) |
d550d98d | 253 | return -ENODEV; |
1da177e4 | 254 | |
aa650bbd | 255 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Alarm set to %d\n", battery->alarm)); |
d550d98d | 256 | return 0; |
1da177e4 LT |
257 | } |
258 | ||
b6ce4083 | 259 | static int acpi_battery_init_alarm(struct acpi_battery *battery) |
1da177e4 | 260 | { |
4be44fcd LB |
261 | acpi_status status = AE_OK; |
262 | acpi_handle handle = NULL; | |
4be44fcd | 263 | |
b6ce4083 | 264 | /* See if alarms are supported, and if so, set default */ |
f1d4661a AS |
265 | status = acpi_get_handle(battery->device->handle, "_BTP", &handle); |
266 | if (ACPI_FAILURE(status)) { | |
038fdea2 | 267 | battery->alarm_present = 0; |
f1d4661a | 268 | return 0; |
b6ce4083 | 269 | } |
f1d4661a AS |
270 | battery->alarm_present = 1; |
271 | if (!battery->alarm) | |
272 | battery->alarm = battery->design_capacity_warning; | |
aa650bbd | 273 | return acpi_battery_set_alarm(battery); |
b6ce4083 | 274 | } |
1da177e4 | 275 | |
f1d4661a | 276 | static int acpi_battery_update(struct acpi_battery *battery) |
b6ce4083 | 277 | { |
f1d4661a AS |
278 | int saved_present = acpi_battery_present(battery); |
279 | int result = acpi_battery_get_status(battery); | |
280 | if (result || !acpi_battery_present(battery)) | |
b6ce4083 | 281 | return result; |
f1d4661a AS |
282 | if (saved_present != acpi_battery_present(battery) || |
283 | !battery->update_time) { | |
284 | battery->update_time = 0; | |
b6ce4083 VL |
285 | result = acpi_battery_get_info(battery); |
286 | if (result) | |
287 | return result; | |
b6ce4083 VL |
288 | acpi_battery_init_alarm(battery); |
289 | } | |
f1d4661a | 290 | return acpi_battery_get_state(battery); |
4bd35cdb VL |
291 | } |
292 | ||
1da177e4 LT |
293 | /* -------------------------------------------------------------------------- |
294 | FS Interface (/proc) | |
295 | -------------------------------------------------------------------------- */ | |
296 | ||
4be44fcd | 297 | static struct proc_dir_entry *acpi_battery_dir; |
b6ce4083 | 298 | |
78490d82 | 299 | static int acpi_battery_print_info(struct seq_file *seq, int result) |
1da177e4 | 300 | { |
50dd0969 | 301 | struct acpi_battery *battery = seq->private; |
1da177e4 | 302 | |
b6ce4083 | 303 | if (result) |
1da177e4 LT |
304 | goto end; |
305 | ||
aa650bbd AS |
306 | seq_printf(seq, "present: %s\n", |
307 | acpi_battery_present(battery)?"yes":"no"); | |
308 | if (!acpi_battery_present(battery)) | |
1da177e4 | 309 | goto end; |
038fdea2 | 310 | if (battery->design_capacity == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
311 | seq_printf(seq, "design capacity: unknown\n"); |
312 | else | |
313 | seq_printf(seq, "design capacity: %d %sh\n", | |
aa650bbd AS |
314 | battery->design_capacity, |
315 | acpi_battery_units(battery)); | |
1da177e4 | 316 | |
038fdea2 | 317 | if (battery->last_full_capacity == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
318 | seq_printf(seq, "last full capacity: unknown\n"); |
319 | else | |
320 | seq_printf(seq, "last full capacity: %d %sh\n", | |
aa650bbd AS |
321 | battery->last_full_capacity, |
322 | acpi_battery_units(battery)); | |
323 | ||
324 | seq_printf(seq, "battery technology: %srechargeable\n", | |
325 | (!battery->technology)?"non-":""); | |
1da177e4 | 326 | |
038fdea2 | 327 | if (battery->design_voltage == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
328 | seq_printf(seq, "design voltage: unknown\n"); |
329 | else | |
330 | seq_printf(seq, "design voltage: %d mV\n", | |
aa650bbd | 331 | battery->design_voltage); |
1da177e4 | 332 | seq_printf(seq, "design capacity warning: %d %sh\n", |
aa650bbd AS |
333 | battery->design_capacity_warning, |
334 | acpi_battery_units(battery)); | |
1da177e4 | 335 | seq_printf(seq, "design capacity low: %d %sh\n", |
aa650bbd AS |
336 | battery->design_capacity_low, |
337 | acpi_battery_units(battery)); | |
1da177e4 | 338 | seq_printf(seq, "capacity granularity 1: %d %sh\n", |
aa650bbd AS |
339 | battery->capacity_granularity_1, |
340 | acpi_battery_units(battery)); | |
1da177e4 | 341 | seq_printf(seq, "capacity granularity 2: %d %sh\n", |
aa650bbd AS |
342 | battery->capacity_granularity_2, |
343 | acpi_battery_units(battery)); | |
038fdea2 AS |
344 | seq_printf(seq, "model number: %s\n", battery->model_number); |
345 | seq_printf(seq, "serial number: %s\n", battery->serial_number); | |
346 | seq_printf(seq, "battery type: %s\n", battery->type); | |
347 | seq_printf(seq, "OEM info: %s\n", battery->oem_info); | |
4be44fcd | 348 | end: |
b6ce4083 VL |
349 | if (result) |
350 | seq_printf(seq, "ERROR: Unable to read battery info\n"); | |
b6ce4083 VL |
351 | return result; |
352 | } | |
353 | ||
78490d82 | 354 | static int acpi_battery_print_state(struct seq_file *seq, int result) |
1da177e4 | 355 | { |
50dd0969 | 356 | struct acpi_battery *battery = seq->private; |
1da177e4 | 357 | |
b6ce4083 | 358 | if (result) |
1da177e4 LT |
359 | goto end; |
360 | ||
aa650bbd AS |
361 | seq_printf(seq, "present: %s\n", |
362 | acpi_battery_present(battery)?"yes":"no"); | |
363 | if (!acpi_battery_present(battery)) | |
1da177e4 | 364 | goto end; |
b6ce4083 | 365 | |
aa650bbd AS |
366 | seq_printf(seq, "capacity state: %s\n", |
367 | (battery->state & 0x04)?"critical":"ok"); | |
368 | if ((battery->state & 0x01) && (battery->state & 0x02)) | |
4be44fcd LB |
369 | seq_printf(seq, |
370 | "charging state: charging/discharging\n"); | |
aa650bbd | 371 | else if (battery->state & 0x01) |
1da177e4 | 372 | seq_printf(seq, "charging state: discharging\n"); |
038fdea2 | 373 | else if (battery->state & 0x02) |
1da177e4 | 374 | seq_printf(seq, "charging state: charging\n"); |
aa650bbd | 375 | else |
1da177e4 | 376 | seq_printf(seq, "charging state: charged\n"); |
1da177e4 | 377 | |
038fdea2 | 378 | if (battery->present_rate == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
379 | seq_printf(seq, "present rate: unknown\n"); |
380 | else | |
381 | seq_printf(seq, "present rate: %d %s\n", | |
aa650bbd | 382 | battery->present_rate, acpi_battery_units(battery)); |
1da177e4 | 383 | |
038fdea2 | 384 | if (battery->remaining_capacity == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
385 | seq_printf(seq, "remaining capacity: unknown\n"); |
386 | else | |
387 | seq_printf(seq, "remaining capacity: %d %sh\n", | |
aa650bbd | 388 | battery->remaining_capacity, acpi_battery_units(battery)); |
038fdea2 | 389 | if (battery->present_voltage == ACPI_BATTERY_VALUE_UNKNOWN) |
1da177e4 LT |
390 | seq_printf(seq, "present voltage: unknown\n"); |
391 | else | |
392 | seq_printf(seq, "present voltage: %d mV\n", | |
aa650bbd | 393 | battery->present_voltage); |
4be44fcd | 394 | end: |
aa650bbd | 395 | if (result) |
b6ce4083 | 396 | seq_printf(seq, "ERROR: Unable to read battery state\n"); |
b6ce4083 VL |
397 | |
398 | return result; | |
399 | } | |
400 | ||
78490d82 | 401 | static int acpi_battery_print_alarm(struct seq_file *seq, int result) |
1da177e4 | 402 | { |
50dd0969 | 403 | struct acpi_battery *battery = seq->private; |
1da177e4 | 404 | |
b6ce4083 | 405 | if (result) |
1da177e4 LT |
406 | goto end; |
407 | ||
b6ce4083 | 408 | if (!acpi_battery_present(battery)) { |
1da177e4 LT |
409 | seq_printf(seq, "present: no\n"); |
410 | goto end; | |
411 | } | |
1da177e4 LT |
412 | seq_printf(seq, "alarm: "); |
413 | if (!battery->alarm) | |
414 | seq_printf(seq, "unsupported\n"); | |
415 | else | |
aa650bbd AS |
416 | seq_printf(seq, "%u %sh\n", battery->alarm, |
417 | acpi_battery_units(battery)); | |
4be44fcd | 418 | end: |
b6ce4083 VL |
419 | if (result) |
420 | seq_printf(seq, "ERROR: Unable to read battery alarm\n"); | |
b6ce4083 VL |
421 | return result; |
422 | } | |
423 | ||
f1d4661a AS |
424 | static ssize_t acpi_battery_write_alarm(struct file *file, |
425 | const char __user * buffer, | |
426 | size_t count, loff_t * ppos) | |
1da177e4 | 427 | { |
4be44fcd LB |
428 | int result = 0; |
429 | char alarm_string[12] = { '\0' }; | |
50dd0969 JE |
430 | struct seq_file *m = file->private_data; |
431 | struct acpi_battery *battery = m->private; | |
1da177e4 | 432 | |
1da177e4 | 433 | if (!battery || (count > sizeof(alarm_string) - 1)) |
d550d98d | 434 | return -EINVAL; |
b6ce4083 VL |
435 | if (result) { |
436 | result = -ENODEV; | |
437 | goto end; | |
438 | } | |
b6ce4083 VL |
439 | if (!acpi_battery_present(battery)) { |
440 | result = -ENODEV; | |
441 | goto end; | |
442 | } | |
b6ce4083 VL |
443 | if (copy_from_user(alarm_string, buffer, count)) { |
444 | result = -EFAULT; | |
445 | goto end; | |
446 | } | |
1da177e4 | 447 | alarm_string[count] = '\0'; |
f1d4661a | 448 | battery->alarm = simple_strtol(alarm_string, NULL, 0); |
aa650bbd | 449 | result = acpi_battery_set_alarm(battery); |
b6ce4083 | 450 | end: |
b6ce4083 | 451 | if (!result) |
f1d4661a | 452 | return count; |
78490d82 AS |
453 | return result; |
454 | } | |
455 | ||
456 | typedef int(*print_func)(struct seq_file *seq, int result); | |
aa650bbd AS |
457 | |
458 | static print_func acpi_print_funcs[ACPI_BATTERY_NUMFILES] = { | |
459 | acpi_battery_print_info, | |
460 | acpi_battery_print_state, | |
461 | acpi_battery_print_alarm, | |
78490d82 | 462 | }; |
b6ce4083 | 463 | |
78490d82 AS |
464 | static int acpi_battery_read(int fid, struct seq_file *seq) |
465 | { | |
466 | struct acpi_battery *battery = seq->private; | |
f1d4661a | 467 | int result = acpi_battery_update(battery); |
aa650bbd | 468 | return acpi_print_funcs[fid](seq, result); |
78490d82 AS |
469 | } |
470 | ||
aa650bbd AS |
471 | #define DECLARE_FILE_FUNCTIONS(_name) \ |
472 | static int acpi_battery_read_##_name(struct seq_file *seq, void *offset) \ | |
473 | { \ | |
474 | return acpi_battery_read(_name##_tag, seq); \ | |
475 | } \ | |
476 | static int acpi_battery_##_name##_open_fs(struct inode *inode, struct file *file) \ | |
477 | { \ | |
478 | return single_open(file, acpi_battery_read_##_name, PDE(inode)->data); \ | |
78490d82 AS |
479 | } |
480 | ||
aa650bbd AS |
481 | DECLARE_FILE_FUNCTIONS(info); |
482 | DECLARE_FILE_FUNCTIONS(state); | |
483 | DECLARE_FILE_FUNCTIONS(alarm); | |
484 | ||
485 | #undef DECLARE_FILE_FUNCTIONS | |
486 | ||
487 | #define FILE_DESCRIPTION_RO(_name) \ | |
488 | { \ | |
489 | .name = __stringify(_name), \ | |
490 | .mode = S_IRUGO, \ | |
491 | .ops = { \ | |
492 | .open = acpi_battery_##_name##_open_fs, \ | |
493 | .read = seq_read, \ | |
494 | .llseek = seq_lseek, \ | |
495 | .release = single_release, \ | |
496 | .owner = THIS_MODULE, \ | |
497 | }, \ | |
498 | } | |
78490d82 | 499 | |
aa650bbd AS |
500 | #define FILE_DESCRIPTION_RW(_name) \ |
501 | { \ | |
502 | .name = __stringify(_name), \ | |
503 | .mode = S_IFREG | S_IRUGO | S_IWUSR, \ | |
504 | .ops = { \ | |
505 | .open = acpi_battery_##_name##_open_fs, \ | |
506 | .read = seq_read, \ | |
507 | .llseek = seq_lseek, \ | |
508 | .write = acpi_battery_write_##_name, \ | |
509 | .release = single_release, \ | |
510 | .owner = THIS_MODULE, \ | |
511 | }, \ | |
512 | } | |
1da177e4 | 513 | |
78490d82 AS |
514 | static struct battery_file { |
515 | struct file_operations ops; | |
516 | mode_t mode; | |
517 | char *name; | |
518 | } acpi_battery_file[] = { | |
aa650bbd AS |
519 | FILE_DESCRIPTION_RO(info), |
520 | FILE_DESCRIPTION_RO(state), | |
521 | FILE_DESCRIPTION_RW(alarm), | |
1da177e4 LT |
522 | }; |
523 | ||
aa650bbd AS |
524 | #undef FILE_DESCRIPTION_RO |
525 | #undef FILE_DESCRIPTION_RW | |
526 | ||
4be44fcd | 527 | static int acpi_battery_add_fs(struct acpi_device *device) |
1da177e4 | 528 | { |
4be44fcd | 529 | struct proc_dir_entry *entry = NULL; |
78490d82 | 530 | int i; |
1da177e4 | 531 | |
1da177e4 LT |
532 | if (!acpi_device_dir(device)) { |
533 | acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), | |
4be44fcd | 534 | acpi_battery_dir); |
1da177e4 | 535 | if (!acpi_device_dir(device)) |
d550d98d | 536 | return -ENODEV; |
1da177e4 LT |
537 | acpi_device_dir(device)->owner = THIS_MODULE; |
538 | } | |
539 | ||
78490d82 AS |
540 | for (i = 0; i < ACPI_BATTERY_NUMFILES; ++i) { |
541 | entry = create_proc_entry(acpi_battery_file[i].name, | |
542 | acpi_battery_file[i].mode, acpi_device_dir(device)); | |
543 | if (!entry) | |
544 | return -ENODEV; | |
545 | else { | |
546 | entry->proc_fops = &acpi_battery_file[i].ops; | |
547 | entry->data = acpi_driver_data(device); | |
548 | entry->owner = THIS_MODULE; | |
549 | } | |
1da177e4 | 550 | } |
d550d98d | 551 | return 0; |
1da177e4 LT |
552 | } |
553 | ||
aa650bbd | 554 | static void acpi_battery_remove_fs(struct acpi_device *device) |
1da177e4 | 555 | { |
78490d82 | 556 | int i; |
aa650bbd AS |
557 | if (!acpi_device_dir(device)) |
558 | return; | |
559 | for (i = 0; i < ACPI_BATTERY_NUMFILES; ++i) | |
560 | remove_proc_entry(acpi_battery_file[i].name, | |
1da177e4 | 561 | acpi_device_dir(device)); |
1da177e4 | 562 | |
aa650bbd AS |
563 | remove_proc_entry(acpi_device_bid(device), acpi_battery_dir); |
564 | acpi_device_dir(device) = NULL; | |
1da177e4 LT |
565 | } |
566 | ||
1da177e4 LT |
567 | /* -------------------------------------------------------------------------- |
568 | Driver Interface | |
569 | -------------------------------------------------------------------------- */ | |
570 | ||
4be44fcd | 571 | static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) |
1da177e4 | 572 | { |
50dd0969 | 573 | struct acpi_battery *battery = data; |
f1d4661a | 574 | struct acpi_device *device; |
1da177e4 | 575 | if (!battery) |
d550d98d | 576 | return; |
145def84 | 577 | device = battery->device; |
f1d4661a AS |
578 | acpi_battery_update(battery); |
579 | acpi_bus_generate_proc_event(device, event, | |
580 | acpi_battery_present(battery)); | |
581 | acpi_bus_generate_netlink_event(device->pnp.device_class, | |
582 | device->dev.bus_id, event, | |
9ea7d575 | 583 | acpi_battery_present(battery)); |
1da177e4 LT |
584 | } |
585 | ||
4be44fcd | 586 | static int acpi_battery_add(struct acpi_device *device) |
1da177e4 | 587 | { |
4be44fcd LB |
588 | int result = 0; |
589 | acpi_status status = 0; | |
590 | struct acpi_battery *battery = NULL; | |
1da177e4 | 591 | if (!device) |
d550d98d | 592 | return -EINVAL; |
36bcbec7 | 593 | battery = kzalloc(sizeof(struct acpi_battery), GFP_KERNEL); |
1da177e4 | 594 | if (!battery) |
d550d98d | 595 | return -ENOMEM; |
145def84 | 596 | battery->device = device; |
1da177e4 LT |
597 | strcpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME); |
598 | strcpy(acpi_device_class(device), ACPI_BATTERY_CLASS); | |
599 | acpi_driver_data(device) = battery; | |
038fdea2 | 600 | mutex_init(&battery->lock); |
f1d4661a | 601 | acpi_battery_update(battery); |
1da177e4 LT |
602 | result = acpi_battery_add_fs(device); |
603 | if (result) | |
604 | goto end; | |
3b073ec3 | 605 | status = acpi_install_notify_handler(device->handle, |
9fdae727 | 606 | ACPI_ALL_NOTIFY, |
4be44fcd | 607 | acpi_battery_notify, battery); |
1da177e4 | 608 | if (ACPI_FAILURE(status)) { |
b6ce4083 | 609 | ACPI_EXCEPTION((AE_INFO, status, "Installing notify handler")); |
1da177e4 LT |
610 | result = -ENODEV; |
611 | goto end; | |
612 | } | |
1da177e4 | 613 | printk(KERN_INFO PREFIX "%s Slot [%s] (battery %s)\n", |
4be44fcd LB |
614 | ACPI_BATTERY_DEVICE_NAME, acpi_device_bid(device), |
615 | device->status.battery_present ? "present" : "absent"); | |
4be44fcd | 616 | end: |
1da177e4 LT |
617 | if (result) { |
618 | acpi_battery_remove_fs(device); | |
619 | kfree(battery); | |
620 | } | |
d550d98d | 621 | return result; |
1da177e4 LT |
622 | } |
623 | ||
4be44fcd | 624 | static int acpi_battery_remove(struct acpi_device *device, int type) |
1da177e4 | 625 | { |
4be44fcd LB |
626 | acpi_status status = 0; |
627 | struct acpi_battery *battery = NULL; | |
1da177e4 | 628 | |
1da177e4 | 629 | if (!device || !acpi_driver_data(device)) |
d550d98d | 630 | return -EINVAL; |
50dd0969 | 631 | battery = acpi_driver_data(device); |
3b073ec3 | 632 | status = acpi_remove_notify_handler(device->handle, |
9fdae727 | 633 | ACPI_ALL_NOTIFY, |
4be44fcd | 634 | acpi_battery_notify); |
1da177e4 | 635 | acpi_battery_remove_fs(device); |
038fdea2 | 636 | mutex_destroy(&battery->lock); |
1da177e4 | 637 | kfree(battery); |
d550d98d | 638 | return 0; |
1da177e4 LT |
639 | } |
640 | ||
34c4415a | 641 | /* this is needed to learn about changes made in suspended state */ |
5d9464a4 | 642 | static int acpi_battery_resume(struct acpi_device *device) |
34c4415a JK |
643 | { |
644 | struct acpi_battery *battery; | |
34c4415a JK |
645 | if (!device) |
646 | return -EINVAL; | |
f1d4661a AS |
647 | battery = acpi_driver_data(device); |
648 | battery->update_time = 0; | |
b6ce4083 | 649 | return 0; |
34c4415a JK |
650 | } |
651 | ||
aa650bbd AS |
652 | static struct acpi_driver acpi_battery_driver = { |
653 | .name = "battery", | |
654 | .class = ACPI_BATTERY_CLASS, | |
655 | .ids = battery_device_ids, | |
656 | .ops = { | |
657 | .add = acpi_battery_add, | |
658 | .resume = acpi_battery_resume, | |
659 | .remove = acpi_battery_remove, | |
660 | }, | |
661 | }; | |
662 | ||
4be44fcd | 663 | static int __init acpi_battery_init(void) |
1da177e4 | 664 | { |
4d8316d5 PM |
665 | if (acpi_disabled) |
666 | return -ENODEV; | |
3f86b832 | 667 | acpi_battery_dir = acpi_lock_battery_dir(); |
1da177e4 | 668 | if (!acpi_battery_dir) |
d550d98d | 669 | return -ENODEV; |
aa650bbd | 670 | if (acpi_bus_register_driver(&acpi_battery_driver) < 0) { |
3f86b832 | 671 | acpi_unlock_battery_dir(acpi_battery_dir); |
d550d98d | 672 | return -ENODEV; |
1da177e4 | 673 | } |
d550d98d | 674 | return 0; |
1da177e4 LT |
675 | } |
676 | ||
4be44fcd | 677 | static void __exit acpi_battery_exit(void) |
1da177e4 | 678 | { |
1da177e4 | 679 | acpi_bus_unregister_driver(&acpi_battery_driver); |
3f86b832 | 680 | acpi_unlock_battery_dir(acpi_battery_dir); |
1da177e4 LT |
681 | } |
682 | ||
1da177e4 LT |
683 | module_init(acpi_battery_init); |
684 | module_exit(acpi_battery_exit); |