Fix bug introduced with r28800 (missing interrupt handler).
[maemo-rb.git] / firmware / powermgmt.c
blob6ebbce5423b5204805a788a993aefa510ac50caf
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by Heikki Hannikainen, Uwe Freese
11 * Revisions copyright (C) 2005 by Gerald Van Baren
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "config.h"
23 #include "system.h"
24 #include "kernel.h"
25 #include "thread.h"
26 #include "debug.h"
27 #include "adc.h"
28 #include "string.h"
29 #include "storage.h"
30 #include "power.h"
31 #include "audio.h"
32 #include "mp3_playback.h"
33 #include "usb.h"
34 #include "powermgmt.h"
35 #include "backlight.h"
36 #include "lcd.h"
37 #include "rtc.h"
38 #if CONFIG_TUNER
39 #include "fmradio.h"
40 #endif
41 #include "sound.h"
42 #ifdef HAVE_LCD_BITMAP
43 #include "font.h"
44 #endif
45 #include "logf.h"
46 #include "lcd-remote.h"
47 #if (CONFIG_PLATFORM & PLATFORM_HOSTED)
48 #include <time.h>
49 #endif
51 #if (defined(IAUDIO_X5) || defined(IAUDIO_M5)) && !defined (SIMULATOR)
52 #include "lcd-remote-target.h"
53 #endif
54 #if (defined(IAUDIO_X5) || defined(IAUDIO_M5) || defined(COWON_D2)) \
55 && !defined (SIMULATOR)
56 #include "pcf50606.h"
57 #endif
59 /** Shared by sim **/
60 int last_sent_battery_level = 100;
61 /* battery level (0-100%) */
62 int battery_percent = -1;
63 void send_battery_level_event(void);
65 #if CONFIG_CHARGING
66 /* State of the charger input as seen by the power thread */
67 enum charger_input_state_type charger_input_state;
68 /* Power inputs as seen by the power thread */
69 unsigned int power_thread_inputs;
70 #if CONFIG_CHARGING >= CHARGING_MONITOR
71 /* Charging state (mode) as seen by the power thread */
72 enum charge_state_type charge_state = DISCHARGING;
73 #endif
74 #endif /* CONFIG_CHARGING */
76 #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
77 static int shutdown_timeout = 0;
79 * Average battery voltage and charger voltage, filtered via a digital
80 * exponential filter (aka. exponential moving average, scaled):
81 * avgbat = y[n] = (N-1)/N*y[n-1] + x[n]. battery_millivolts = y[n] / N.
83 static unsigned int avgbat;
84 /* filtered battery voltage, millivolts */
85 static unsigned int battery_millivolts;
86 /* default value, mAh */
87 static int battery_capacity = BATTERY_CAPACITY_DEFAULT;
90 #if BATTERY_TYPES_COUNT > 1
91 static int battery_type = 0;
92 #else
93 #define battery_type 0
94 #endif
96 /* Power history: power_history[0] is the newest sample */
97 unsigned short power_history[POWER_HISTORY_LEN] = {0};
99 #if CONFIG_CPU == JZ4732 /* FIXME! */
100 static char power_stack[DEFAULT_STACK_SIZE + POWERMGMT_DEBUG_STACK];
101 #else
102 static char power_stack[DEFAULT_STACK_SIZE/2 + POWERMGMT_DEBUG_STACK];
103 #endif
104 static const char power_thread_name[] = "power";
106 static int poweroff_timeout = 0;
107 static int powermgmt_est_runningtime_min = -1;
109 static bool sleeptimer_active = false;
110 static long sleeptimer_endtick;
112 static long last_event_tick;
114 static int voltage_to_battery_level(int battery_millivolts);
115 static void battery_status_update(void);
117 #ifdef CURRENT_NORMAL /*only used if we have run current*/
118 static int runcurrent(void);
119 #endif
121 void battery_read_info(int *voltage, int *level)
123 int millivolts = battery_adc_voltage();
125 if (voltage)
126 *voltage = millivolts;
128 if (level)
129 *level = voltage_to_battery_level(millivolts);
132 void reset_poweroff_timer(void)
134 last_event_tick = current_tick;
137 #if BATTERY_TYPES_COUNT > 1
138 void set_battery_type(int type)
140 if (type != battery_type) {
141 if ((unsigned)type >= BATTERY_TYPES_COUNT)
142 type = 0;
144 battery_type = type;
145 battery_status_update(); /* recalculate the battery status */
148 #endif
150 void set_battery_capacity(int capacity)
152 if (capacity > BATTERY_CAPACITY_MAX)
153 capacity = BATTERY_CAPACITY_MAX;
154 if (capacity < BATTERY_CAPACITY_MIN)
155 capacity = BATTERY_CAPACITY_MIN;
157 battery_capacity = capacity;
159 battery_status_update(); /* recalculate the battery status */
162 int get_battery_capacity(void)
164 return battery_capacity;
167 int battery_time(void)
169 return powermgmt_est_runningtime_min;
172 /* Returns battery level in percent */
173 int battery_level(void)
175 #ifdef HAVE_BATTERY_SWITCH
176 if ((power_input_status() & POWER_INPUT_BATTERY) == 0)
177 return -1;
178 #endif
179 return battery_percent;
182 /* Returns filtered battery voltage [millivolts] */
183 unsigned int battery_voltage(void)
185 return battery_millivolts;
188 /* Tells if the battery level is safe for disk writes */
189 bool battery_level_safe(void)
191 #if defined(NO_LOW_BATTERY_SHUTDOWN)
192 return true;
193 #elif defined(HAVE_BATTERY_SWITCH)
194 /* Cannot rely upon the battery reading to be valid and the
195 * device could be powered externally. */
196 return input_millivolts() > battery_level_dangerous[battery_type];
197 #else
198 return battery_millivolts > battery_level_dangerous[battery_type];
199 #endif
202 void set_poweroff_timeout(int timeout)
204 poweroff_timeout = timeout;
207 void set_sleep_timer(int seconds)
209 if (seconds) {
210 sleeptimer_active = true;
211 sleeptimer_endtick = current_tick + seconds * HZ;
213 else {
214 sleeptimer_active = false;
215 sleeptimer_endtick = 0;
219 int get_sleep_timer(void)
221 if (sleeptimer_active && (sleeptimer_endtick >= current_tick))
222 return (sleeptimer_endtick - current_tick) / HZ;
223 else
224 return 0;
227 /* look into the percent_to_volt_* table and get a realistic battery level */
228 static int voltage_to_percent(int voltage, const short* table)
230 if (voltage <= table[0]) {
231 return 0;
233 else if (voltage >= table[10]) {
234 return 100;
236 else {
237 /* search nearest value */
238 int i = 0;
240 while (i < 10 && table[i+1] < voltage)
241 i++;
243 /* interpolate linear between the smaller and greater value */
244 /* Tens digit, 10% per entry, ones digit: interpolated */
245 return i*10 + (voltage - table[i])*10 / (table[i+1] - table[i]);
249 /* update battery level and estimated runtime, called once per minute or
250 * when battery capacity / type settings are changed */
251 static int voltage_to_battery_level(int battery_millivolts)
253 int level;
255 #if CONFIG_CHARGING >= CHARGING_MONITOR
256 if (charging_state()) {
257 /* battery level is defined to be < 100% until charging is finished */
258 level = voltage_to_percent(battery_millivolts,
259 percent_to_volt_charge);
260 if (level > 99)
261 level = 99;
263 else
264 #endif /* CONFIG_CHARGING >= CHARGING_MONITOR */
266 /* DISCHARGING or error state */
267 level = voltage_to_percent(battery_millivolts,
268 percent_to_volt_discharge[battery_type]);
271 return level;
274 static void battery_status_update(void)
276 int level = voltage_to_battery_level(battery_millivolts);
278 #ifdef CURRENT_NORMAL /*don't try to estimate run or charge
279 time without normal current defined*/
280 /* calculate estimated remaining running time */
281 #if CONFIG_CHARGING >= CHARGING_MONITOR
282 if (charging_state()) {
283 /* charging: remaining charging time */
284 powermgmt_est_runningtime_min = (100 - level)*battery_capacity*60
285 / 100 / (CURRENT_MAX_CHG - runcurrent());
287 else
288 #endif
290 /* discharging: remaining running time */
291 if (battery_millivolts > percent_to_volt_discharge[0][0]) {
292 /* linear extrapolation */
293 powermgmt_est_runningtime_min = (level + battery_percent)*60
294 * battery_capacity / 200 / runcurrent();
296 if (0 > powermgmt_est_runningtime_min) {
297 powermgmt_est_runningtime_min = 0;
299 #else
300 powermgmt_est_runningtime_min=-1;
301 #endif
303 battery_percent = level;
304 send_battery_level_event();
308 * We shut off in the following cases:
309 * 1) The unit is idle, not playing music
310 * 2) The unit is playing music, but is paused
311 * 3) The battery level has reached shutdown limit
313 * We do not shut off in the following cases:
314 * 1) The USB is connected
315 * 2) The charger is connected
316 * 3) We are recording, or recording with pause
317 * 4) The radio is playing
319 static void handle_auto_poweroff(void)
321 long timeout = poweroff_timeout*60*HZ;
322 int audio_stat = audio_status();
323 long tick = current_tick;
325 #if CONFIG_CHARGING
327 * Inhibit shutdown as long as the charger is plugged in. If it is
328 * unplugged, wait for a timeout period and then shut down.
330 if (charger_input_state == CHARGER || audio_stat == AUDIO_STATUS_PLAY) {
331 last_event_tick = current_tick;
333 #endif
335 if (!shutdown_timeout && query_force_shutdown()) {
336 backlight_on();
337 sys_poweroff();
340 if (timeout &&
341 #if CONFIG_TUNER
342 !(get_radio_status() & FMRADIO_PLAYING) &&
343 #endif
344 !usb_inserted() &&
345 (audio_stat == 0 ||
346 (audio_stat == (AUDIO_STATUS_PLAY | AUDIO_STATUS_PAUSE) &&
347 !sleeptimer_active))) {
349 if (TIME_AFTER(tick, last_event_tick + timeout) &&
350 TIME_AFTER(tick, storage_last_disk_activity() + timeout)) {
351 sys_poweroff();
354 else if (sleeptimer_active) {
355 /* Handle sleeptimer */
356 if (TIME_AFTER(tick, sleeptimer_endtick)) {
357 audio_stop();
359 if (usb_inserted()
360 #if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
361 || charger_input_state != NO_CHARGER
362 #endif
364 DEBUGF("Sleep timer timeout. Stopping...\n");
365 set_sleep_timer(0);
366 backlight_off(); /* Nighty, nighty... */
368 else {
369 DEBUGF("Sleep timer timeout. Shutting off...\n");
370 sys_poweroff();
376 #ifdef CURRENT_NORMAL /*check that we have a current defined in a config file*/
379 * Estimate how much current we are drawing just to run.
381 static int runcurrent(void)
383 int current = CURRENT_NORMAL;
385 #ifndef BOOTLOADER
386 if (usb_inserted()
387 #ifdef HAVE_USB_POWER
388 #if (CURRENT_USB < CURRENT_NORMAL)
389 || usb_powered()
390 #else
391 && !usb_powered()
392 #endif
393 #endif
395 current = CURRENT_USB;
398 #if defined(HAVE_BACKLIGHT)
399 if (backlight_get_current_timeout() == 0) /* LED always on */
400 current += CURRENT_BACKLIGHT;
401 #endif
403 #if defined(HAVE_RECORDING) && defined(CURRENT_RECORD)
404 if (audio_status() & AUDIO_STATUS_RECORD)
405 current += CURRENT_RECORD;
406 #endif
408 #ifdef HAVE_SPDIF_POWER
409 if (spdif_powered())
410 current += CURRENT_SPDIF_OUT;
411 #endif
413 #ifdef HAVE_REMOTE_LCD
414 if (remote_detect())
415 current += CURRENT_REMOTE;
416 #endif
418 #if defined(HAVE_ATA_POWER_OFF) && defined(CURRENT_ATA)
419 if (ide_powered())
420 current += CURRENT_ATA;
421 #endif
423 #endif /* BOOTLOADER */
425 return current;
428 #endif /* CURRENT_NORMAL */
430 /* Check to see whether or not we've received an alarm in the last second */
431 #ifdef HAVE_RTC_ALARM
432 static void power_thread_rtc_process(void)
434 if (rtc_check_alarm_flag())
435 rtc_enable_alarm(false);
437 #endif
439 /* switch off unit if battery level is too low for reliable operation */
440 bool query_force_shutdown(void)
442 #if defined(NO_LOW_BATTERY_SHUTDOWN)
443 return false;
444 #elif defined(HAVE_BATTERY_SWITCH)
445 /* Cannot rely upon the battery reading to be valid and the
446 * device could be powered externally. */
447 return input_millivolts() < battery_level_shutoff[battery_type];
448 #else
449 return battery_millivolts < battery_level_shutoff[battery_type];
450 #endif
453 #if defined(HAVE_BATTERY_SWITCH) || defined(HAVE_RESET_BATTERY_FILTER)
455 * Reset the battery voltage filter to a new value and update the
456 * status.
458 void reset_battery_filter(int millivolts)
460 avgbat = millivolts * BATT_AVE_SAMPLES;
461 battery_millivolts = millivolts;
462 battery_status_update();
464 #endif /* HAVE_BATTERY_SWITCH */
466 /** Generic charging algorithms for common charging types **/
467 #if CONFIG_CHARGING == 0 || CONFIG_CHARGING == CHARGING_SIMPLE
468 static inline void powermgmt_init_target(void)
470 /* Nothing to do */
473 static inline void charging_algorithm_step(void)
475 /* Nothing to do */
478 static inline void charging_algorithm_close(void)
480 /* Nothing to do */
482 #elif CONFIG_CHARGING == CHARGING_MONITOR
484 * Monitor CHARGING/DISCHARGING state.
486 static inline void powermgmt_init_target(void)
488 /* Nothing to do */
491 static inline void charging_algorithm_step(void)
493 switch (charger_input_state)
495 case CHARGER_PLUGGED:
496 case CHARGER:
497 if (charging_state()) {
498 charge_state = CHARGING;
499 break;
501 /* Fallthrough */
502 case CHARGER_UNPLUGGED:
503 case NO_CHARGER:
504 charge_state = DISCHARGING;
505 break;
509 static inline void charging_algorithm_close(void)
511 /* Nothing to do */
513 #endif /* CONFIG_CHARGING == * */
515 #if CONFIG_CHARGING
516 /* Shortcut function calls - compatibility, simplicity. */
518 /* Returns true if any power input is capable of charging. */
519 bool charger_inserted(void)
521 return power_thread_inputs & POWER_INPUT_CHARGER;
524 /* Returns true if any power input is connected - charging-capable
525 * or not. */
526 bool power_input_present(void)
528 return power_thread_inputs & POWER_INPUT;
532 * Detect charger inserted. Return true if the state is transistional.
534 static inline bool detect_charger(unsigned int pwr)
537 * Detect charger plugged/unplugged transitions. On a plugged or
538 * unplugged event, we return immediately, run once through the main
539 * loop (including the subroutines), and end up back here where we
540 * transition to the appropriate steady state charger on/off state.
542 if (pwr & POWER_INPUT_CHARGER) {
543 switch (charger_input_state)
545 case NO_CHARGER:
546 case CHARGER_UNPLUGGED:
547 charger_input_state = CHARGER_PLUGGED;
548 break;
550 case CHARGER_PLUGGED:
551 queue_broadcast(SYS_CHARGER_CONNECTED, 0);
552 last_sent_battery_level = 0;
553 charger_input_state = CHARGER;
554 break;
556 case CHARGER:
557 /* Steady state */
558 return false;
561 else { /* charger not inserted */
562 switch (charger_input_state)
564 case NO_CHARGER:
565 /* Steady state */
566 return false;
568 case CHARGER_UNPLUGGED:
569 queue_broadcast(SYS_CHARGER_DISCONNECTED, 0);
570 last_sent_battery_level = 100;
571 charger_input_state = NO_CHARGER;
572 break;
574 case CHARGER_PLUGGED:
575 case CHARGER:
576 charger_input_state = CHARGER_UNPLUGGED;
577 break;
581 /* Transitional state */
582 return true;
584 #endif /* CONFIG_CHARGING */
587 * Monitor the presence of a charger and perform critical frequent steps
588 * such as running the battery voltage filter.
590 static inline void power_thread_step(void)
592 /* If the power off timeout expires, the main thread has failed
593 to shut down the system, and we need to force a power off */
594 if (shutdown_timeout) {
595 shutdown_timeout -= POWER_THREAD_STEP_TICKS;
597 if (shutdown_timeout <= 0)
598 power_off();
601 #ifdef HAVE_RTC_ALARM
602 power_thread_rtc_process();
603 #endif
606 * Do a digital exponential filter. We don't sample the battery if
607 * the disk is spinning unless we are in USB mode (the disk will most
608 * likely always be spinning in USB mode) or charging.
610 if (!storage_disk_is_active() || usb_inserted()
611 #if CONFIG_CHARGING >= CHARGING_MONITOR
612 || charger_input_state == CHARGER
613 #endif
615 avgbat += battery_adc_voltage() - avgbat / BATT_AVE_SAMPLES;
617 * battery_millivolts is the millivolt-scaled filtered battery value.
619 battery_millivolts = avgbat / BATT_AVE_SAMPLES;
621 /* update battery status every time an update is available */
622 battery_status_update();
624 else if (battery_percent < 8) {
626 * If battery is low, observe voltage during disk activity.
627 * Shut down if voltage drops below shutoff level and we are not
628 * using NiMH or Alkaline batteries.
630 battery_millivolts = (battery_adc_voltage() +
631 battery_millivolts + 1) / 2;
633 /* update battery status every time an update is available */
634 battery_status_update();
636 if (!shutdown_timeout && query_force_shutdown()) {
637 sys_poweroff();
639 else {
640 avgbat += battery_millivolts - avgbat / BATT_AVE_SAMPLES;
643 } /* power_thread_step */
645 static void power_thread(void)
647 long next_power_hist;
649 /* Delay reading the first battery level */
650 #ifdef MROBE_100
651 while (battery_adc_voltage() > 4200) /* gives false readings initially */
652 #endif
654 sleep(HZ/100);
657 #if CONFIG_CHARGING
658 /* Initialize power input status before calling other routines. */
659 power_thread_inputs = power_input_status();
660 #endif
662 /* initialize the voltages for the exponential filter */
663 avgbat = battery_adc_voltage() + 15;
665 #ifdef HAVE_DISK_STORAGE /* this adjustment is only needed for HD based */
666 /* The battery voltage is usually a little lower directly after
667 turning on, because the disk was used heavily. Raise it by 5% */
668 #if CONFIG_CHARGING
669 if (!charger_inserted()) /* only if charger not connected */
670 #endif
672 avgbat += (percent_to_volt_discharge[battery_type][6] -
673 percent_to_volt_discharge[battery_type][5]) / 2;
675 #endif /* HAVE_DISK_STORAGE */
677 avgbat = avgbat * BATT_AVE_SAMPLES;
678 battery_millivolts = avgbat / BATT_AVE_SAMPLES;
679 power_history[0] = battery_millivolts;
681 #if CONFIG_CHARGING
682 if (charger_inserted()) {
683 battery_percent = voltage_to_percent(battery_millivolts,
684 percent_to_volt_charge);
686 else
687 #endif
689 battery_percent = voltage_to_percent(battery_millivolts,
690 percent_to_volt_discharge[battery_type]);
691 battery_percent += battery_percent < 100;
694 powermgmt_init_target();
696 next_power_hist = current_tick + HZ*60;
698 while (1)
700 #if CONFIG_CHARGING
701 unsigned int pwr = power_input_status();
702 #ifdef HAVE_BATTERY_SWITCH
703 if ((pwr ^ power_thread_inputs) & POWER_INPUT_BATTERY) {
704 sleep(HZ/10);
705 reset_battery_filter(battery_adc_voltage());
707 #endif
708 power_thread_inputs = pwr;
710 if (!detect_charger(pwr))
711 #endif /* CONFIG_CHARGING */
713 /* Steady state */
714 sleep(POWER_THREAD_STEP_TICKS);
716 /* Do common power tasks */
717 power_thread_step();
720 /* Perform target tasks */
721 charging_algorithm_step();
723 if (TIME_BEFORE(current_tick, next_power_hist))
724 continue;
726 /* increment to ensure there is a record for every minute
727 * rather than go forward from the current tick */
728 next_power_hist += HZ*60;
730 /* rotate the power history */
731 memmove(&power_history[1], &power_history[0],
732 sizeof(power_history) - sizeof(power_history[0]));
734 /* insert new value at the start, in millivolts 8-) */
735 power_history[0] = battery_millivolts;
737 handle_auto_poweroff();
739 } /* power_thread */
741 void powermgmt_init(void)
743 create_thread(power_thread, power_stack, sizeof(power_stack), 0,
744 power_thread_name IF_PRIO(, PRIORITY_SYSTEM)
745 IF_COP(, CPU));
748 /* Various hardware housekeeping tasks relating to shutting down the player */
749 void shutdown_hw(void)
751 charging_algorithm_close();
752 audio_stop();
754 if (battery_level_safe()) { /* do not save on critical battery */
755 #ifdef HAVE_LCD_BITMAP
756 glyph_cache_save(NULL);
757 #endif
759 /* Commit pending writes if needed. Even though we don't do write caching,
760 things like flash translation layers may need this to commit scattered
761 pages to there final locations. So far only used for iPod Nano 2G. */
762 #ifdef HAVE_STORAGE_FLUSH
763 storage_flush();
764 #endif
766 if (storage_disk_is_active())
767 storage_spindown(1);
770 while (storage_disk_is_active())
771 sleep(HZ/10);
773 #if CONFIG_CODEC == SWCODEC
774 audiohw_close();
775 #else
776 mp3_shutdown();
777 #endif
779 /* If HD is still active we try to wait for spindown, otherwise the
780 shutdown_timeout in power_thread_step will force a power off */
781 while (storage_disk_is_active())
782 sleep(HZ/10);
784 #ifndef HAVE_LCD_COLOR
785 lcd_set_contrast(0);
786 #endif
787 #ifdef HAVE_REMOTE_LCD
788 lcd_remote_set_contrast(0);
789 #endif
790 #ifdef HAVE_LCD_SHUTDOWN
791 lcd_shutdown();
792 #endif
794 /* Small delay to make sure all HW gets time to flush. Especially
795 eeprom chips are quite slow and might be still writing the last
796 byte. */
797 sleep(HZ/4);
798 power_off();
801 void sys_poweroff(void)
803 #ifndef BOOTLOADER
804 logf("sys_poweroff()");
805 /* If the main thread fails to shut down the system, we will force a
806 power off after an 20 second timeout - 28 seconds if recording */
807 if (shutdown_timeout == 0) {
808 #if defined(IAUDIO_X5) || defined(IAUDIO_M5) || defined(COWON_D2)
809 pcf50606_reset_timeout(); /* Reset timer on first attempt only */
810 #endif
811 #ifdef HAVE_RECORDING
812 if (audio_status() & AUDIO_STATUS_RECORD)
813 shutdown_timeout += HZ*8;
814 #endif
815 #ifdef IPOD_NANO2G
816 /* The FTL alone may take half a minute to shut down cleanly. */
817 shutdown_timeout += HZ*60;
818 #else
819 shutdown_timeout += HZ*20;
820 #endif
823 queue_broadcast(SYS_POWEROFF, 0);
824 #endif /* BOOTLOADER */
827 void cancel_shutdown(void)
829 logf("cancel_shutdown()");
831 #if defined(IAUDIO_X5) || defined(IAUDIO_M5) || defined(COWON_D2)
832 /* TODO: Move some things to target/ tree */
833 if (shutdown_timeout)
834 pcf50606_reset_timeout();
835 #endif
837 shutdown_timeout = 0;
839 #endif /* PLATFORM_NATIVE */
841 /* Send system battery level update events on reaching certain significant
842 levels. This must be called after battery_percent has been updated. */
843 void send_battery_level_event(void)
845 static const int levels[] = { 5, 15, 30, 50, 0 };
846 const int *level = levels;
848 while (*level)
850 if (battery_percent <= *level && last_sent_battery_level > *level) {
851 last_sent_battery_level = *level;
852 queue_broadcast(SYS_BATTERY_UPDATE, last_sent_battery_level);
853 break;
856 level++;