Commit | Line | Data |
---|---|---|
b7557de4 RP |
1 | /* |
2 | * Battery and Power Management code for the Sharp SL-C7xx and SL-Cxx00 | |
3 | * series of PDAs | |
4 | * | |
5 | * Copyright (c) 2004-2005 Richard Purdie | |
6 | * | |
7 | * Based on code written by Sharp for 2.4 kernels | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | */ | |
14 | ||
15 | #undef DEBUG | |
16 | ||
17 | #include <linux/module.h> | |
18 | #include <linux/timer.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/apm_bios.h> | |
22 | #include <linux/delay.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/platform_device.h> | |
181bf8aa | 25 | #include <linux/leds.h> |
d608e52c | 26 | #include <linux/apm-emulation.h> |
95d9ffbe | 27 | #include <linux/suspend.h> |
b7557de4 | 28 | |
a09e64fb | 29 | #include <mach/hardware.h> |
b7557de4 | 30 | #include <asm/irq.h> |
a09e64fb RK |
31 | #include <mach/pm.h> |
32 | #include <mach/pxa-regs.h> | |
33 | #include <mach/pxa2xx-regs.h> | |
34 | #include <mach/sharpsl.h> | |
b7557de4 RP |
35 | #include <asm/hardware/sharpsl_pm.h> |
36 | ||
37 | /* | |
38 | * Constants | |
39 | */ | |
40 | #define SHARPSL_CHARGE_ON_TIME_INTERVAL (msecs_to_jiffies(1*60*1000)) /* 1 min */ | |
41 | #define SHARPSL_CHARGE_FINISH_TIME (msecs_to_jiffies(10*60*1000)) /* 10 min */ | |
42 | #define SHARPSL_BATCHK_TIME (msecs_to_jiffies(15*1000)) /* 15 sec */ | |
43 | #define SHARPSL_BATCHK_TIME_SUSPEND (60*10) /* 10 min */ | |
576b3ef2 | 44 | |
b7557de4 RP |
45 | #define SHARPSL_WAIT_CO_TIME 15 /* 15 sec */ |
46 | #define SHARPSL_WAIT_DISCHARGE_ON 100 /* 100 msec */ | |
47 | #define SHARPSL_CHECK_BATTERY_WAIT_TIME_TEMP 10 /* 10 msec */ | |
48 | #define SHARPSL_CHECK_BATTERY_WAIT_TIME_VOLT 10 /* 10 msec */ | |
49 | #define SHARPSL_CHECK_BATTERY_WAIT_TIME_ACIN 10 /* 10 msec */ | |
50 | #define SHARPSL_CHARGE_WAIT_TIME 15 /* 15 msec */ | |
51 | #define SHARPSL_CHARGE_CO_CHECK_TIME 5 /* 5 msec */ | |
52 | #define SHARPSL_CHARGE_RETRY_CNT 1 /* eqv. 10 min */ | |
53 | ||
b7557de4 RP |
54 | /* |
55 | * Prototypes | |
56 | */ | |
56e7d85c | 57 | #ifdef CONFIG_PM |
b7557de4 | 58 | static int sharpsl_off_charge_battery(void); |
b7557de4 | 59 | static int sharpsl_check_battery_voltage(void); |
b7557de4 | 60 | static int sharpsl_fatal_check(void); |
56e7d85c DB |
61 | #endif |
62 | static int sharpsl_check_battery_temp(void); | |
63 | static int sharpsl_ac_check(void); | |
b7557de4 RP |
64 | static int sharpsl_average_value(int ad); |
65 | static void sharpsl_average_clear(void); | |
6d5aefb8 DH |
66 | static void sharpsl_charge_toggle(struct work_struct *private_); |
67 | static void sharpsl_battery_thread(struct work_struct *private_); | |
b7557de4 RP |
68 | |
69 | ||
70 | /* | |
71 | * Variables | |
72 | */ | |
73 | struct sharpsl_pm_status sharpsl_pm; | |
6d5aefb8 DH |
74 | DECLARE_DELAYED_WORK(toggle_charger, sharpsl_charge_toggle); |
75 | DECLARE_DELAYED_WORK(sharpsl_bat, sharpsl_battery_thread); | |
181bf8aa | 76 | DEFINE_LED_TRIGGER(sharpsl_charge_led_trigger); |
b7557de4 RP |
77 | |
78 | ||
79 | static int get_percentage(int voltage) | |
80 | { | |
81 | int i = sharpsl_pm.machinfo->bat_levels - 1; | |
f8703dc8 | 82 | int bl_status = sharpsl_pm.machinfo->backlight_get_status ? sharpsl_pm.machinfo->backlight_get_status() : 0; |
b7557de4 RP |
83 | struct battery_thresh *thresh; |
84 | ||
85 | if (sharpsl_pm.charge_mode == CHRG_ON) | |
f8703dc8 | 86 | thresh = bl_status ? sharpsl_pm.machinfo->bat_levels_acin_bl : sharpsl_pm.machinfo->bat_levels_acin; |
b7557de4 | 87 | else |
f8703dc8 | 88 | thresh = bl_status ? sharpsl_pm.machinfo->bat_levels_noac_bl : sharpsl_pm.machinfo->bat_levels_noac; |
b7557de4 RP |
89 | |
90 | while (i > 0 && (voltage > thresh[i].voltage)) | |
91 | i--; | |
92 | ||
93 | return thresh[i].percentage; | |
94 | } | |
95 | ||
96 | static int get_apm_status(int voltage) | |
97 | { | |
98 | int low_thresh, high_thresh; | |
99 | ||
100 | if (sharpsl_pm.charge_mode == CHRG_ON) { | |
101 | high_thresh = sharpsl_pm.machinfo->status_high_acin; | |
102 | low_thresh = sharpsl_pm.machinfo->status_low_acin; | |
103 | } else { | |
104 | high_thresh = sharpsl_pm.machinfo->status_high_noac; | |
105 | low_thresh = sharpsl_pm.machinfo->status_low_noac; | |
106 | } | |
107 | ||
108 | if (voltage >= high_thresh) | |
109 | return APM_BATTERY_STATUS_HIGH; | |
110 | if (voltage >= low_thresh) | |
111 | return APM_BATTERY_STATUS_LOW; | |
112 | return APM_BATTERY_STATUS_CRITICAL; | |
113 | } | |
114 | ||
115 | void sharpsl_battery_kick(void) | |
116 | { | |
117 | schedule_delayed_work(&sharpsl_bat, msecs_to_jiffies(125)); | |
118 | } | |
119 | EXPORT_SYMBOL(sharpsl_battery_kick); | |
120 | ||
121 | ||
6d5aefb8 | 122 | static void sharpsl_battery_thread(struct work_struct *private_) |
b7557de4 RP |
123 | { |
124 | int voltage, percent, apm_status, i = 0; | |
125 | ||
126 | if (!sharpsl_pm.machinfo) | |
127 | return; | |
128 | ||
129 | sharpsl_pm.battstat.ac_status = (sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN) ? APM_AC_ONLINE : APM_AC_OFFLINE); | |
130 | ||
131 | /* Corgi cannot confirm when battery fully charged so periodically kick! */ | |
f8703dc8 | 132 | if (!sharpsl_pm.machinfo->batfull_irq && (sharpsl_pm.charge_mode == CHRG_ON) |
b7557de4 | 133 | && time_after(jiffies, sharpsl_pm.charge_start_time + SHARPSL_CHARGE_ON_TIME_INTERVAL)) |
6d5aefb8 | 134 | schedule_delayed_work(&toggle_charger, 0); |
b7557de4 RP |
135 | |
136 | while(1) { | |
137 | voltage = sharpsl_pm.machinfo->read_devdata(SHARPSL_BATT_VOLT); | |
138 | ||
139 | if (voltage > 0) break; | |
140 | if (i++ > 5) { | |
141 | voltage = sharpsl_pm.machinfo->bat_levels_noac[0].voltage; | |
142 | dev_warn(sharpsl_pm.dev, "Warning: Cannot read main battery!\n"); | |
143 | break; | |
144 | } | |
145 | } | |
146 | ||
147 | voltage = sharpsl_average_value(voltage); | |
148 | apm_status = get_apm_status(voltage); | |
149 | percent = get_percentage(voltage); | |
150 | ||
151 | /* At low battery voltages, the voltage has a tendency to start | |
152 | creeping back up so we try to avoid this here */ | |
153 | if ((sharpsl_pm.battstat.ac_status == APM_AC_ONLINE) || (apm_status == APM_BATTERY_STATUS_HIGH) || percent <= sharpsl_pm.battstat.mainbat_percent) { | |
154 | sharpsl_pm.battstat.mainbat_voltage = voltage; | |
155 | sharpsl_pm.battstat.mainbat_status = apm_status; | |
156 | sharpsl_pm.battstat.mainbat_percent = percent; | |
157 | } | |
158 | ||
aceb6f0b | 159 | dev_dbg(sharpsl_pm.dev, "Battery: voltage: %d, status: %d, percentage: %d, time: %ld\n", voltage, |
b7557de4 RP |
160 | sharpsl_pm.battstat.mainbat_status, sharpsl_pm.battstat.mainbat_percent, jiffies); |
161 | ||
02a8e769 | 162 | #ifdef CONFIG_BACKLIGHT_CORGI |
b7557de4 RP |
163 | /* If battery is low. limit backlight intensity to save power. */ |
164 | if ((sharpsl_pm.battstat.ac_status != APM_AC_ONLINE) | |
165 | && ((sharpsl_pm.battstat.mainbat_status == APM_BATTERY_STATUS_LOW) || | |
166 | (sharpsl_pm.battstat.mainbat_status == APM_BATTERY_STATUS_CRITICAL))) { | |
167 | if (!(sharpsl_pm.flags & SHARPSL_BL_LIMIT)) { | |
f8703dc8 | 168 | sharpsl_pm.machinfo->backlight_limit(1); |
b7557de4 RP |
169 | sharpsl_pm.flags |= SHARPSL_BL_LIMIT; |
170 | } | |
171 | } else if (sharpsl_pm.flags & SHARPSL_BL_LIMIT) { | |
f8703dc8 | 172 | sharpsl_pm.machinfo->backlight_limit(0); |
b7557de4 RP |
173 | sharpsl_pm.flags &= ~SHARPSL_BL_LIMIT; |
174 | } | |
02a8e769 | 175 | #endif |
b7557de4 RP |
176 | |
177 | /* Suspend if critical battery level */ | |
178 | if ((sharpsl_pm.battstat.ac_status != APM_AC_ONLINE) | |
179 | && (sharpsl_pm.battstat.mainbat_status == APM_BATTERY_STATUS_CRITICAL) | |
180 | && !(sharpsl_pm.flags & SHARPSL_APM_QUEUED)) { | |
181 | sharpsl_pm.flags |= SHARPSL_APM_QUEUED; | |
182 | dev_err(sharpsl_pm.dev, "Fatal Off\n"); | |
183 | apm_queue_event(APM_CRITICAL_SUSPEND); | |
184 | } | |
185 | ||
186 | schedule_delayed_work(&sharpsl_bat, SHARPSL_BATCHK_TIME); | |
187 | } | |
188 | ||
189 | void sharpsl_pm_led(int val) | |
190 | { | |
191 | if (val == SHARPSL_LED_ERROR) { | |
192 | dev_err(sharpsl_pm.dev, "Charging Error!\n"); | |
193 | } else if (val == SHARPSL_LED_ON) { | |
194 | dev_dbg(sharpsl_pm.dev, "Charge LED On\n"); | |
181bf8aa | 195 | led_trigger_event(sharpsl_charge_led_trigger, LED_FULL); |
b7557de4 RP |
196 | } else { |
197 | dev_dbg(sharpsl_pm.dev, "Charge LED Off\n"); | |
181bf8aa | 198 | led_trigger_event(sharpsl_charge_led_trigger, LED_OFF); |
b7557de4 RP |
199 | } |
200 | } | |
201 | ||
202 | static void sharpsl_charge_on(void) | |
203 | { | |
204 | dev_dbg(sharpsl_pm.dev, "Turning Charger On\n"); | |
205 | ||
206 | sharpsl_pm.full_count = 0; | |
207 | sharpsl_pm.charge_mode = CHRG_ON; | |
208 | schedule_delayed_work(&toggle_charger, msecs_to_jiffies(250)); | |
209 | schedule_delayed_work(&sharpsl_bat, msecs_to_jiffies(500)); | |
210 | } | |
211 | ||
212 | static void sharpsl_charge_off(void) | |
213 | { | |
214 | dev_dbg(sharpsl_pm.dev, "Turning Charger Off\n"); | |
215 | ||
216 | sharpsl_pm.machinfo->charge(0); | |
217 | sharpsl_pm_led(SHARPSL_LED_OFF); | |
218 | sharpsl_pm.charge_mode = CHRG_OFF; | |
219 | ||
6d5aefb8 | 220 | schedule_delayed_work(&sharpsl_bat, 0); |
b7557de4 RP |
221 | } |
222 | ||
223 | static void sharpsl_charge_error(void) | |
224 | { | |
225 | sharpsl_pm_led(SHARPSL_LED_ERROR); | |
226 | sharpsl_pm.machinfo->charge(0); | |
227 | sharpsl_pm.charge_mode = CHRG_ERROR; | |
228 | } | |
229 | ||
6d5aefb8 | 230 | static void sharpsl_charge_toggle(struct work_struct *private_) |
b7557de4 RP |
231 | { |
232 | dev_dbg(sharpsl_pm.dev, "Toogling Charger at time: %lx\n", jiffies); | |
233 | ||
234 | if (!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN)) { | |
235 | sharpsl_charge_off(); | |
236 | return; | |
237 | } else if ((sharpsl_check_battery_temp() < 0) || (sharpsl_ac_check() < 0)) { | |
238 | sharpsl_charge_error(); | |
239 | return; | |
240 | } | |
241 | ||
242 | sharpsl_pm_led(SHARPSL_LED_ON); | |
243 | sharpsl_pm.machinfo->charge(0); | |
244 | mdelay(SHARPSL_CHARGE_WAIT_TIME); | |
245 | sharpsl_pm.machinfo->charge(1); | |
246 | ||
247 | sharpsl_pm.charge_start_time = jiffies; | |
248 | } | |
249 | ||
250 | static void sharpsl_ac_timer(unsigned long data) | |
251 | { | |
252 | int acin = sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN); | |
253 | ||
254 | dev_dbg(sharpsl_pm.dev, "AC Status: %d\n",acin); | |
255 | ||
256 | sharpsl_average_clear(); | |
257 | if (acin && (sharpsl_pm.charge_mode != CHRG_ON)) | |
258 | sharpsl_charge_on(); | |
259 | else if (sharpsl_pm.charge_mode == CHRG_ON) | |
260 | sharpsl_charge_off(); | |
261 | ||
6d5aefb8 | 262 | schedule_delayed_work(&sharpsl_bat, 0); |
b7557de4 RP |
263 | } |
264 | ||
265 | ||
0cd61b68 | 266 | irqreturn_t sharpsl_ac_isr(int irq, void *dev_id) |
b7557de4 RP |
267 | { |
268 | /* Delay the event slightly to debounce */ | |
269 | /* Must be a smaller delay than the chrg_full_isr below */ | |
270 | mod_timer(&sharpsl_pm.ac_timer, jiffies + msecs_to_jiffies(250)); | |
271 | ||
272 | return IRQ_HANDLED; | |
273 | } | |
274 | ||
275 | static void sharpsl_chrg_full_timer(unsigned long data) | |
276 | { | |
277 | dev_dbg(sharpsl_pm.dev, "Charge Full at time: %lx\n", jiffies); | |
278 | ||
279 | sharpsl_pm.full_count++; | |
280 | ||
281 | if (!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN)) { | |
282 | dev_dbg(sharpsl_pm.dev, "Charge Full: AC removed - stop charging!\n"); | |
283 | if (sharpsl_pm.charge_mode == CHRG_ON) | |
284 | sharpsl_charge_off(); | |
285 | } else if (sharpsl_pm.full_count < 2) { | |
286 | dev_dbg(sharpsl_pm.dev, "Charge Full: Count too low\n"); | |
6d5aefb8 | 287 | schedule_delayed_work(&toggle_charger, 0); |
b7557de4 RP |
288 | } else if (time_after(jiffies, sharpsl_pm.charge_start_time + SHARPSL_CHARGE_FINISH_TIME)) { |
289 | dev_dbg(sharpsl_pm.dev, "Charge Full: Interrupt generated too slowly - retry.\n"); | |
6d5aefb8 | 290 | schedule_delayed_work(&toggle_charger, 0); |
b7557de4 RP |
291 | } else { |
292 | sharpsl_charge_off(); | |
293 | sharpsl_pm.charge_mode = CHRG_DONE; | |
294 | dev_dbg(sharpsl_pm.dev, "Charge Full: Charging Finished\n"); | |
295 | } | |
296 | } | |
297 | ||
298 | /* Charging Finished Interrupt (Not present on Corgi) */ | |
6cbdc8c5 | 299 | /* Can trigger at the same time as an AC status change so |
b7557de4 | 300 | delay until after that has been processed */ |
0cd61b68 | 301 | irqreturn_t sharpsl_chrg_full_isr(int irq, void *dev_id) |
b7557de4 RP |
302 | { |
303 | if (sharpsl_pm.flags & SHARPSL_SUSPENDED) | |
304 | return IRQ_HANDLED; | |
305 | ||
306 | /* delay until after any ac interrupt */ | |
307 | mod_timer(&sharpsl_pm.chrg_full_timer, jiffies + msecs_to_jiffies(500)); | |
308 | ||
309 | return IRQ_HANDLED; | |
310 | } | |
311 | ||
0cd61b68 | 312 | irqreturn_t sharpsl_fatal_isr(int irq, void *dev_id) |
b7557de4 RP |
313 | { |
314 | int is_fatal = 0; | |
315 | ||
316 | if (!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_LOCK)) { | |
317 | dev_err(sharpsl_pm.dev, "Battery now Unlocked! Suspending.\n"); | |
318 | is_fatal = 1; | |
319 | } | |
320 | ||
321 | if (!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_FATAL)) { | |
322 | dev_err(sharpsl_pm.dev, "Fatal Batt Error! Suspending.\n"); | |
323 | is_fatal = 1; | |
324 | } | |
325 | ||
326 | if (!(sharpsl_pm.flags & SHARPSL_APM_QUEUED) && is_fatal) { | |
327 | sharpsl_pm.flags |= SHARPSL_APM_QUEUED; | |
328 | apm_queue_event(APM_CRITICAL_SUSPEND); | |
329 | } | |
330 | ||
331 | return IRQ_HANDLED; | |
332 | } | |
333 | ||
334 | /* | |
335 | * Maintain an average of the last 10 readings | |
336 | */ | |
337 | #define SHARPSL_CNV_VALUE_NUM 10 | |
338 | static int sharpsl_ad_index; | |
339 | ||
340 | static void sharpsl_average_clear(void) | |
341 | { | |
342 | sharpsl_ad_index = 0; | |
343 | } | |
344 | ||
345 | static int sharpsl_average_value(int ad) | |
346 | { | |
347 | int i, ad_val = 0; | |
348 | static int sharpsl_ad[SHARPSL_CNV_VALUE_NUM+1]; | |
349 | ||
350 | if (sharpsl_pm.battstat.mainbat_status != APM_BATTERY_STATUS_HIGH) { | |
351 | sharpsl_ad_index = 0; | |
352 | return ad; | |
353 | } | |
354 | ||
355 | sharpsl_ad[sharpsl_ad_index] = ad; | |
356 | sharpsl_ad_index++; | |
357 | if (sharpsl_ad_index >= SHARPSL_CNV_VALUE_NUM) { | |
358 | for (i=0; i < (SHARPSL_CNV_VALUE_NUM-1); i++) | |
359 | sharpsl_ad[i] = sharpsl_ad[i+1]; | |
360 | sharpsl_ad_index = SHARPSL_CNV_VALUE_NUM - 1; | |
361 | } | |
362 | for (i=0; i < sharpsl_ad_index; i++) | |
363 | ad_val += sharpsl_ad[i]; | |
364 | ||
365 | return (ad_val / sharpsl_ad_index); | |
366 | } | |
367 | ||
368 | /* | |
369 | * Take an array of 5 integers, remove the maximum and minimum values | |
370 | * and return the average. | |
371 | */ | |
372 | static int get_select_val(int *val) | |
373 | { | |
374 | int i, j, k, temp, sum = 0; | |
375 | ||
376 | /* Find MAX val */ | |
377 | temp = val[0]; | |
378 | j=0; | |
379 | for (i=1; i<5; i++) { | |
380 | if (temp < val[i]) { | |
381 | temp = val[i]; | |
382 | j = i; | |
383 | } | |
384 | } | |
385 | ||
386 | /* Find MIN val */ | |
387 | temp = val[4]; | |
388 | k=4; | |
389 | for (i=3; i>=0; i--) { | |
390 | if (temp > val[i]) { | |
391 | temp = val[i]; | |
392 | k = i; | |
393 | } | |
394 | } | |
395 | ||
396 | for (i=0; i<5; i++) | |
397 | if (i != j && i != k ) | |
398 | sum += val[i]; | |
399 | ||
400 | dev_dbg(sharpsl_pm.dev, "Average: %d from values: %d, %d, %d, %d, %d\n", sum/3, val[0], val[1], val[2], val[3], val[4]); | |
401 | ||
402 | return (sum/3); | |
403 | } | |
404 | ||
405 | static int sharpsl_check_battery_temp(void) | |
406 | { | |
407 | int val, i, buff[5]; | |
408 | ||
409 | /* Check battery temperature */ | |
410 | for (i=0; i<5; i++) { | |
411 | mdelay(SHARPSL_CHECK_BATTERY_WAIT_TIME_TEMP); | |
412 | sharpsl_pm.machinfo->measure_temp(1); | |
413 | mdelay(SHARPSL_CHECK_BATTERY_WAIT_TIME_TEMP); | |
414 | buff[i] = sharpsl_pm.machinfo->read_devdata(SHARPSL_BATT_TEMP); | |
415 | sharpsl_pm.machinfo->measure_temp(0); | |
416 | } | |
417 | ||
418 | val = get_select_val(buff); | |
419 | ||
420 | dev_dbg(sharpsl_pm.dev, "Temperature: %d\n", val); | |
2704f0e6 PM |
421 | if (val > sharpsl_pm.machinfo->charge_on_temp) { |
422 | printk(KERN_WARNING "Not charging: temperature out of limits.\n"); | |
b7557de4 | 423 | return -1; |
2704f0e6 | 424 | } |
b7557de4 RP |
425 | |
426 | return 0; | |
427 | } | |
428 | ||
56e7d85c | 429 | #ifdef CONFIG_PM |
b7557de4 RP |
430 | static int sharpsl_check_battery_voltage(void) |
431 | { | |
432 | int val, i, buff[5]; | |
433 | ||
434 | /* disable charge, enable discharge */ | |
435 | sharpsl_pm.machinfo->charge(0); | |
436 | sharpsl_pm.machinfo->discharge(1); | |
437 | mdelay(SHARPSL_WAIT_DISCHARGE_ON); | |
438 | ||
439 | if (sharpsl_pm.machinfo->discharge1) | |
440 | sharpsl_pm.machinfo->discharge1(1); | |
441 | ||
442 | /* Check battery voltage */ | |
443 | for (i=0; i<5; i++) { | |
444 | buff[i] = sharpsl_pm.machinfo->read_devdata(SHARPSL_BATT_VOLT); | |
445 | mdelay(SHARPSL_CHECK_BATTERY_WAIT_TIME_VOLT); | |
446 | } | |
447 | ||
448 | if (sharpsl_pm.machinfo->discharge1) | |
449 | sharpsl_pm.machinfo->discharge1(0); | |
450 | ||
451 | sharpsl_pm.machinfo->discharge(0); | |
452 | ||
453 | val = get_select_val(buff); | |
454 | dev_dbg(sharpsl_pm.dev, "Battery Voltage: %d\n", val); | |
455 | ||
f8703dc8 | 456 | if (val < sharpsl_pm.machinfo->charge_on_volt) |
b7557de4 RP |
457 | return -1; |
458 | ||
459 | return 0; | |
460 | } | |
56e7d85c | 461 | #endif |
b7557de4 RP |
462 | |
463 | static int sharpsl_ac_check(void) | |
464 | { | |
465 | int temp, i, buff[5]; | |
466 | ||
467 | for (i=0; i<5; i++) { | |
468 | buff[i] = sharpsl_pm.machinfo->read_devdata(SHARPSL_ACIN_VOLT); | |
469 | mdelay(SHARPSL_CHECK_BATTERY_WAIT_TIME_ACIN); | |
470 | } | |
471 | ||
472 | temp = get_select_val(buff); | |
473 | dev_dbg(sharpsl_pm.dev, "AC Voltage: %d\n",temp); | |
474 | ||
f8703dc8 | 475 | if ((temp > sharpsl_pm.machinfo->charge_acin_high) || (temp < sharpsl_pm.machinfo->charge_acin_low)) { |
b7557de4 RP |
476 | dev_err(sharpsl_pm.dev, "Error: AC check failed.\n"); |
477 | return -1; | |
478 | } | |
479 | ||
480 | return 0; | |
481 | } | |
482 | ||
483 | #ifdef CONFIG_PM | |
484 | static int sharpsl_pm_suspend(struct platform_device *pdev, pm_message_t state) | |
485 | { | |
486 | sharpsl_pm.flags |= SHARPSL_SUSPENDED; | |
487 | flush_scheduled_work(); | |
488 | ||
489 | if (sharpsl_pm.charge_mode == CHRG_ON) | |
490 | sharpsl_pm.flags |= SHARPSL_DO_OFFLINE_CHRG; | |
491 | else | |
492 | sharpsl_pm.flags &= ~SHARPSL_DO_OFFLINE_CHRG; | |
493 | ||
494 | return 0; | |
495 | } | |
496 | ||
497 | static int sharpsl_pm_resume(struct platform_device *pdev) | |
498 | { | |
499 | /* Clear the reset source indicators as they break the bootloader upon reboot */ | |
500 | RCSR = 0x0f; | |
501 | sharpsl_average_clear(); | |
502 | sharpsl_pm.flags &= ~SHARPSL_APM_QUEUED; | |
503 | sharpsl_pm.flags &= ~SHARPSL_SUSPENDED; | |
504 | ||
505 | return 0; | |
506 | } | |
507 | ||
508 | static void corgi_goto_sleep(unsigned long alarm_time, unsigned int alarm_enable, suspend_state_t state) | |
509 | { | |
510 | dev_dbg(sharpsl_pm.dev, "Time is: %08x\n",RCNR); | |
511 | ||
512 | dev_dbg(sharpsl_pm.dev, "Offline Charge Activate = %d\n",sharpsl_pm.flags & SHARPSL_DO_OFFLINE_CHRG); | |
513 | /* not charging and AC-IN! */ | |
514 | ||
515 | if ((sharpsl_pm.flags & SHARPSL_DO_OFFLINE_CHRG) && (sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN))) { | |
516 | dev_dbg(sharpsl_pm.dev, "Activating Offline Charger...\n"); | |
517 | sharpsl_pm.charge_mode = CHRG_OFF; | |
518 | sharpsl_pm.flags &= ~SHARPSL_DO_OFFLINE_CHRG; | |
519 | sharpsl_off_charge_battery(); | |
520 | } | |
521 | ||
522 | sharpsl_pm.machinfo->presuspend(); | |
523 | ||
524 | PEDR = 0xffffffff; /* clear it */ | |
525 | ||
526 | sharpsl_pm.flags &= ~SHARPSL_ALARM_ACTIVE; | |
527 | if ((sharpsl_pm.charge_mode == CHRG_ON) && ((alarm_enable && ((alarm_time - RCNR) > (SHARPSL_BATCHK_TIME_SUSPEND + 30))) || !alarm_enable)) { | |
528 | RTSR &= RTSR_ALE; | |
529 | RTAR = RCNR + SHARPSL_BATCHK_TIME_SUSPEND; | |
530 | dev_dbg(sharpsl_pm.dev, "Charging alarm at: %08x\n",RTAR); | |
531 | sharpsl_pm.flags |= SHARPSL_ALARM_ACTIVE; | |
532 | } else if (alarm_enable) { | |
533 | RTSR &= RTSR_ALE; | |
534 | RTAR = alarm_time; | |
535 | dev_dbg(sharpsl_pm.dev, "User alarm at: %08x\n",RTAR); | |
536 | } else { | |
537 | dev_dbg(sharpsl_pm.dev, "No alarms set.\n"); | |
538 | } | |
539 | ||
540 | pxa_pm_enter(state); | |
541 | ||
542 | sharpsl_pm.machinfo->postsuspend(); | |
543 | ||
544 | dev_dbg(sharpsl_pm.dev, "Corgi woken up from suspend: %08x\n",PEDR); | |
545 | } | |
546 | ||
547 | static int corgi_enter_suspend(unsigned long alarm_time, unsigned int alarm_enable, suspend_state_t state) | |
548 | { | |
549 | if (!sharpsl_pm.machinfo->should_wakeup(!(sharpsl_pm.flags & SHARPSL_ALARM_ACTIVE) && alarm_enable) ) | |
550 | { | |
551 | if (!(sharpsl_pm.flags & SHARPSL_ALARM_ACTIVE)) { | |
552 | dev_dbg(sharpsl_pm.dev, "No user triggered wakeup events and not charging. Strange. Suspend.\n"); | |
553 | corgi_goto_sleep(alarm_time, alarm_enable, state); | |
554 | return 1; | |
555 | } | |
556 | if(sharpsl_off_charge_battery()) { | |
557 | dev_dbg(sharpsl_pm.dev, "Charging. Suspend...\n"); | |
558 | corgi_goto_sleep(alarm_time, alarm_enable, state); | |
559 | return 1; | |
560 | } | |
561 | dev_dbg(sharpsl_pm.dev, "User triggered wakeup in offline charger.\n"); | |
562 | } | |
563 | ||
564 | if ((!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_LOCK)) || (sharpsl_fatal_check() < 0) ) | |
565 | { | |
566 | dev_err(sharpsl_pm.dev, "Fatal condition. Suspend.\n"); | |
567 | corgi_goto_sleep(alarm_time, alarm_enable, state); | |
568 | return 1; | |
569 | } | |
570 | ||
571 | return 0; | |
572 | } | |
573 | ||
574 | static int corgi_pxa_pm_enter(suspend_state_t state) | |
575 | { | |
576 | unsigned long alarm_time = RTAR; | |
577 | unsigned int alarm_status = ((RTSR & RTSR_ALE) != 0); | |
578 | ||
579 | dev_dbg(sharpsl_pm.dev, "SharpSL suspending for first time.\n"); | |
580 | ||
581 | corgi_goto_sleep(alarm_time, alarm_status, state); | |
582 | ||
583 | while (corgi_enter_suspend(alarm_time,alarm_status,state)) | |
584 | {} | |
585 | ||
576b3ef2 DO |
586 | if (sharpsl_pm.machinfo->earlyresume) |
587 | sharpsl_pm.machinfo->earlyresume(); | |
588 | ||
b7557de4 RP |
589 | dev_dbg(sharpsl_pm.dev, "SharpSL resuming...\n"); |
590 | ||
591 | return 0; | |
592 | } | |
b7557de4 RP |
593 | |
594 | /* | |
595 | * Check for fatal battery errors | |
596 | * Fatal returns -1 | |
597 | */ | |
598 | static int sharpsl_fatal_check(void) | |
599 | { | |
600 | int buff[5], temp, i, acin; | |
601 | ||
602 | dev_dbg(sharpsl_pm.dev, "sharpsl_fatal_check entered\n"); | |
603 | ||
604 | /* Check AC-Adapter */ | |
605 | acin = sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN); | |
606 | ||
607 | if (acin && (sharpsl_pm.charge_mode == CHRG_ON)) { | |
608 | sharpsl_pm.machinfo->charge(0); | |
609 | udelay(100); | |
610 | sharpsl_pm.machinfo->discharge(1); /* enable discharge */ | |
611 | mdelay(SHARPSL_WAIT_DISCHARGE_ON); | |
612 | } | |
613 | ||
614 | if (sharpsl_pm.machinfo->discharge1) | |
615 | sharpsl_pm.machinfo->discharge1(1); | |
616 | ||
617 | /* Check battery : check inserting battery ? */ | |
618 | for (i=0; i<5; i++) { | |
619 | buff[i] = sharpsl_pm.machinfo->read_devdata(SHARPSL_BATT_VOLT); | |
620 | mdelay(SHARPSL_CHECK_BATTERY_WAIT_TIME_VOLT); | |
621 | } | |
622 | ||
623 | if (sharpsl_pm.machinfo->discharge1) | |
624 | sharpsl_pm.machinfo->discharge1(0); | |
625 | ||
626 | if (acin && (sharpsl_pm.charge_mode == CHRG_ON)) { | |
627 | udelay(100); | |
628 | sharpsl_pm.machinfo->charge(1); | |
629 | sharpsl_pm.machinfo->discharge(0); | |
630 | } | |
631 | ||
632 | temp = get_select_val(buff); | |
aceb6f0b | 633 | dev_dbg(sharpsl_pm.dev, "sharpsl_fatal_check: acin: %d, discharge voltage: %d, no discharge: %ld\n", acin, temp, sharpsl_pm.machinfo->read_devdata(SHARPSL_BATT_VOLT)); |
b7557de4 | 634 | |
f8703dc8 RP |
635 | if ((acin && (temp < sharpsl_pm.machinfo->fatal_acin_volt)) || |
636 | (!acin && (temp < sharpsl_pm.machinfo->fatal_noacin_volt))) | |
b7557de4 RP |
637 | return -1; |
638 | return 0; | |
639 | } | |
640 | ||
641 | static int sharpsl_off_charge_error(void) | |
642 | { | |
6cbdc8c5 | 643 | dev_err(sharpsl_pm.dev, "Offline Charger: Error occurred.\n"); |
b7557de4 RP |
644 | sharpsl_pm.machinfo->charge(0); |
645 | sharpsl_pm_led(SHARPSL_LED_ERROR); | |
646 | sharpsl_pm.charge_mode = CHRG_ERROR; | |
647 | return 1; | |
648 | } | |
649 | ||
650 | /* | |
651 | * Charging Control while suspended | |
652 | * Return 1 - go straight to sleep | |
653 | * Return 0 - sleep or wakeup depending on other factors | |
654 | */ | |
655 | static int sharpsl_off_charge_battery(void) | |
656 | { | |
657 | int time; | |
658 | ||
659 | dev_dbg(sharpsl_pm.dev, "Charge Mode: %d\n", sharpsl_pm.charge_mode); | |
660 | ||
661 | if (sharpsl_pm.charge_mode == CHRG_OFF) { | |
662 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Step 1\n"); | |
663 | ||
664 | /* AC Check */ | |
665 | if ((sharpsl_ac_check() < 0) || (sharpsl_check_battery_temp() < 0)) | |
666 | return sharpsl_off_charge_error(); | |
667 | ||
668 | /* Start Charging */ | |
669 | sharpsl_pm_led(SHARPSL_LED_ON); | |
670 | sharpsl_pm.machinfo->charge(0); | |
671 | mdelay(SHARPSL_CHARGE_WAIT_TIME); | |
672 | sharpsl_pm.machinfo->charge(1); | |
673 | ||
674 | sharpsl_pm.charge_mode = CHRG_ON; | |
675 | sharpsl_pm.full_count = 0; | |
676 | ||
677 | return 1; | |
678 | } else if (sharpsl_pm.charge_mode != CHRG_ON) { | |
679 | return 1; | |
680 | } | |
681 | ||
682 | if (sharpsl_pm.full_count == 0) { | |
683 | int time; | |
684 | ||
685 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Step 2\n"); | |
686 | ||
687 | if ((sharpsl_check_battery_temp() < 0) || (sharpsl_check_battery_voltage() < 0)) | |
688 | return sharpsl_off_charge_error(); | |
689 | ||
690 | sharpsl_pm.machinfo->charge(0); | |
691 | mdelay(SHARPSL_CHARGE_WAIT_TIME); | |
692 | sharpsl_pm.machinfo->charge(1); | |
693 | sharpsl_pm.charge_mode = CHRG_ON; | |
694 | ||
695 | mdelay(SHARPSL_CHARGE_CO_CHECK_TIME); | |
696 | ||
697 | time = RCNR; | |
698 | while(1) { | |
6cbdc8c5 | 699 | /* Check if any wakeup event had occurred */ |
b7557de4 RP |
700 | if (sharpsl_pm.machinfo->charger_wakeup() != 0) |
701 | return 0; | |
702 | /* Check for timeout */ | |
703 | if ((RCNR - time) > SHARPSL_WAIT_CO_TIME) | |
704 | return 1; | |
705 | if (sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_CHRGFULL)) { | |
6cbdc8c5 | 706 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Charge full occurred. Retrying to check\n"); |
b7557de4 RP |
707 | sharpsl_pm.full_count++; |
708 | sharpsl_pm.machinfo->charge(0); | |
709 | mdelay(SHARPSL_CHARGE_WAIT_TIME); | |
710 | sharpsl_pm.machinfo->charge(1); | |
711 | return 1; | |
712 | } | |
713 | } | |
714 | } | |
715 | ||
716 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Step 3\n"); | |
717 | ||
718 | mdelay(SHARPSL_CHARGE_CO_CHECK_TIME); | |
719 | ||
720 | time = RCNR; | |
721 | while(1) { | |
6cbdc8c5 | 722 | /* Check if any wakeup event had occurred */ |
b7557de4 RP |
723 | if (sharpsl_pm.machinfo->charger_wakeup() != 0) |
724 | return 0; | |
725 | /* Check for timeout */ | |
726 | if ((RCNR-time) > SHARPSL_WAIT_CO_TIME) { | |
727 | if (sharpsl_pm.full_count > SHARPSL_CHARGE_RETRY_CNT) { | |
728 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Not charged sufficiently. Retrying.\n"); | |
729 | sharpsl_pm.full_count = 0; | |
730 | } | |
731 | sharpsl_pm.full_count++; | |
732 | return 1; | |
733 | } | |
734 | if (sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_CHRGFULL)) { | |
735 | dev_dbg(sharpsl_pm.dev, "Offline Charger: Charging complete.\n"); | |
736 | sharpsl_pm_led(SHARPSL_LED_OFF); | |
737 | sharpsl_pm.machinfo->charge(0); | |
738 | sharpsl_pm.charge_mode = CHRG_DONE; | |
739 | return 1; | |
740 | } | |
741 | } | |
742 | } | |
56e7d85c DB |
743 | #else |
744 | #define sharpsl_pm_suspend NULL | |
745 | #define sharpsl_pm_resume NULL | |
746 | #endif | |
b7557de4 RP |
747 | |
748 | static ssize_t battery_percentage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
749 | { | |
750 | return sprintf(buf, "%d\n",sharpsl_pm.battstat.mainbat_percent); | |
751 | } | |
752 | ||
753 | static ssize_t battery_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) | |
754 | { | |
755 | return sprintf(buf, "%d\n",sharpsl_pm.battstat.mainbat_voltage); | |
756 | } | |
757 | ||
758 | static DEVICE_ATTR(battery_percentage, 0444, battery_percentage_show, NULL); | |
759 | static DEVICE_ATTR(battery_voltage, 0444, battery_voltage_show, NULL); | |
760 | ||
761 | extern void (*apm_get_power_status)(struct apm_power_info *); | |
762 | ||
763 | static void sharpsl_apm_get_power_status(struct apm_power_info *info) | |
764 | { | |
765 | info->ac_line_status = sharpsl_pm.battstat.ac_status; | |
766 | ||
767 | if (sharpsl_pm.charge_mode == CHRG_ON) | |
768 | info->battery_status = APM_BATTERY_STATUS_CHARGING; | |
769 | else | |
770 | info->battery_status = sharpsl_pm.battstat.mainbat_status; | |
771 | ||
772 | info->battery_flag = (1 << info->battery_status); | |
773 | info->battery_life = sharpsl_pm.battstat.mainbat_percent; | |
774 | } | |
775 | ||
56e7d85c | 776 | #ifdef CONFIG_PM |
26398a70 | 777 | static struct platform_suspend_ops sharpsl_pm_ops = { |
b7557de4 | 778 | .enter = corgi_pxa_pm_enter, |
26398a70 | 779 | .valid = suspend_valid_only_mem, |
b7557de4 | 780 | }; |
56e7d85c | 781 | #endif |
b7557de4 RP |
782 | |
783 | static int __init sharpsl_pm_probe(struct platform_device *pdev) | |
784 | { | |
aceb6f0b RP |
785 | int ret; |
786 | ||
b7557de4 RP |
787 | if (!pdev->dev.platform_data) |
788 | return -EINVAL; | |
789 | ||
790 | sharpsl_pm.dev = &pdev->dev; | |
791 | sharpsl_pm.machinfo = pdev->dev.platform_data; | |
792 | sharpsl_pm.charge_mode = CHRG_OFF; | |
793 | sharpsl_pm.flags = 0; | |
794 | ||
795 | init_timer(&sharpsl_pm.ac_timer); | |
796 | sharpsl_pm.ac_timer.function = sharpsl_ac_timer; | |
797 | ||
798 | init_timer(&sharpsl_pm.chrg_full_timer); | |
799 | sharpsl_pm.chrg_full_timer.function = sharpsl_chrg_full_timer; | |
800 | ||
181bf8aa RP |
801 | led_trigger_register_simple("sharpsl-charge", &sharpsl_charge_led_trigger); |
802 | ||
b7557de4 RP |
803 | sharpsl_pm.machinfo->init(); |
804 | ||
aceb6f0b RP |
805 | ret = device_create_file(&pdev->dev, &dev_attr_battery_percentage); |
806 | ret |= device_create_file(&pdev->dev, &dev_attr_battery_voltage); | |
807 | if (ret != 0) | |
808 | dev_warn(&pdev->dev, "Failed to register attributes (%d)\n", ret); | |
b7557de4 RP |
809 | |
810 | apm_get_power_status = sharpsl_apm_get_power_status; | |
811 | ||
56e7d85c | 812 | #ifdef CONFIG_PM |
26398a70 | 813 | suspend_set_ops(&sharpsl_pm_ops); |
56e7d85c | 814 | #endif |
b7557de4 RP |
815 | |
816 | mod_timer(&sharpsl_pm.ac_timer, jiffies + msecs_to_jiffies(250)); | |
817 | ||
818 | return 0; | |
819 | } | |
820 | ||
821 | static int sharpsl_pm_remove(struct platform_device *pdev) | |
822 | { | |
26398a70 | 823 | suspend_set_ops(NULL); |
b7557de4 RP |
824 | |
825 | device_remove_file(&pdev->dev, &dev_attr_battery_percentage); | |
826 | device_remove_file(&pdev->dev, &dev_attr_battery_voltage); | |
827 | ||
181bf8aa RP |
828 | led_trigger_unregister_simple(sharpsl_charge_led_trigger); |
829 | ||
b7557de4 RP |
830 | sharpsl_pm.machinfo->exit(); |
831 | ||
832 | del_timer_sync(&sharpsl_pm.chrg_full_timer); | |
833 | del_timer_sync(&sharpsl_pm.ac_timer); | |
834 | ||
835 | return 0; | |
836 | } | |
837 | ||
838 | static struct platform_driver sharpsl_pm_driver = { | |
839 | .probe = sharpsl_pm_probe, | |
840 | .remove = sharpsl_pm_remove, | |
841 | .suspend = sharpsl_pm_suspend, | |
842 | .resume = sharpsl_pm_resume, | |
843 | .driver = { | |
844 | .name = "sharpsl-pm", | |
845 | }, | |
846 | }; | |
847 | ||
848 | static int __devinit sharpsl_pm_init(void) | |
849 | { | |
850 | return platform_driver_register(&sharpsl_pm_driver); | |
851 | } | |
852 | ||
853 | static void sharpsl_pm_exit(void) | |
854 | { | |
855 | platform_driver_unregister(&sharpsl_pm_driver); | |
856 | } | |
857 | ||
858 | late_initcall(sharpsl_pm_init); | |
859 | module_exit(sharpsl_pm_exit); |