/* Zipit power/lid/idle monitor daemon control. * (c) 2005 Chris Studholme * GPL * * Todo: * - monitor keyboard for activity and call event script when idle * - charge indicator does go off when battery is charged! * - monitor battery level and call event script for low/critical battery * - support resume for network activity, then sleep when network idle * - support periodic (timed) wakeups * - read configuration from /etc/power/powerd.conf or some other file */ #include #include #include #include #include #include #include #include #include #include #include #include /* configuration: should be read from some file somewhere */ /* number of seconds between polling of hardware state (lid/ac/etc) */ static const int poll_interval = 2; /* number of seconds between attempts to sleep */ static const int sleep_interval = 60; /* number of seconds between attempts to wakeup (between failed calls * to resume script) */ static const int wake_interval = 2; /* number of seconds before console is consider idle */ static const int idle_threshold = 60; /* set to 1 to ensure accurate timekeeping when sleeping */ static const int sleep_sync = 1; /* number of seconds to wait before auto-wakeup, 0 to disable */ static const int sleep_timeout = 0; /* set to 1 to wakeup on incoming wireless (lan) packet */ static const int sleep_wol = 0; /* master control script to call: * arg 1 -- event * power - power status changed * blank - blank lcd * unblank - restore lcd * suspend - machine about to sleep * resume - wakeup from sleep * * arg 2 -- current power status * ac - ac adapter plugged in * battery - running on battery * low - battery is low * critical - battery is seconds from depletion * * arg 3 -- lid status * open - lid is open * close - lid is closed * * arg 4 -- idle seconds * # - number of seconds since last activity */ static const char* power_script = "/etc/power/event"; #include "hardware.h" volatile unsigned char* pbdr = (unsigned char*)(CLPS7111_VIRT_BASE+PBDR); volatile unsigned char* pbddr = (unsigned char*)(CLPS7111_VIRT_BASE+PBDDR); volatile unsigned char* pddr = (unsigned char*)(CLPS7111_VIRT_BASE+PDDR); volatile unsigned char* ledflsh = (unsigned char*)(CLPS7111_VIRT_BASE+LEDFLSH); volatile unsigned int* rtc = (unsigned int*)(CLPS7111_VIRT_BASE+RTCDR); volatile unsigned int* rtcmr = (unsigned int*)(CLPS7111_VIRT_BASE+RTCMR); volatile unsigned int* stdby = (unsigned int*)(CLPS7111_VIRT_BASE+STDBY); volatile unsigned int* intmr1 = (unsigned int*)(CLPS7111_VIRT_BASE+INTMR1); volatile unsigned int* intmr2 = (unsigned int*)(CLPS7111_VIRT_BASE+INTMR2); volatile unsigned int* intmr3 = (unsigned int*)(CLPS7111_VIRT_BASE+INTMR3); volatile unsigned int* rtceoi = (unsigned int*)(CLPS7111_VIRT_BASE+RTCEOI); volatile unsigned int* kbdeoi = (unsigned int*)(CLPS7111_VIRT_BASE+KBDEOI); volatile unsigned int* syscon1 = (unsigned int*)(CLPS7111_VIRT_BASE+SYSCON1); volatile unsigned int* syscon2 = (unsigned int*)(CLPS7111_VIRT_BASE+SYSCON2); volatile unsigned int* syscon3 = (unsigned int*)(CLPS7111_VIRT_BASE+SYSCON3); /* Put processor to sleep. * * If sync is non-zero, busy waits are added before and after sleep to sync * with RTC and ensure kernel clock can be accurately updated. * * If timeout>0, auto wakeup in timeout seconds. * * If wol is non-zero, incoming network packets will cause wakeup. * * The following functions are performed: * - green LED is forced off * - orange LED blinks briefly once every 4 seconds * - LCD is shut down * - timer interrupt is disabled * - keyboard wakeup is enabled: * a. if lid is closed, any key including lid will wake * b. if lid is open, any key except Enter/BS/;/L/lid will wake * - actual sleep * * On wakeup all hardware is restored to previous state and kernel clock * is updated to account for slept time. If sync is zero, the kernel can * gain or lose up to one second per sleep. */ static void do_sleep(int sync, int timeout, int wol) { // turn off green LED unsigned char pbddr_save = *pbddr; *pbddr |= 2; unsigned char pbdr_save = *pbdr; *pbdr &= ~2; // setup orange/yellow LED to flash unsigned char pddr_save = *pddr; *ledflsh = 64 + 4 + 3; // save syscon and intmr registers unsigned int syscon1_save = *syscon1; unsigned int syscon2_save = *syscon2; unsigned int intmr1_save = *intmr1; unsigned int intmr2_save = *intmr2; // disable LCD *pddr &= ~(1<<5); *syscon1 &= ~(1<<12); // turn off DAI??? Is this audio? What do I do? // wait for rtc edge if (sync) { unsigned int now = *rtc; while (now==*rtc) ; } unsigned int start = *rtc; // disable timer interrupt *intmr1 &= ~(1<<9); // disable net interrupt if !wol if (!wol) *intmr1 &= ~(1<<5); // disable external interrupt 1 // keyboard interrupt if (*pbdr&8) { // lid open; we'll miss wakeup on Enter/BS/;/L/lid *syscon2 = syscon2_save|2; // only use first 6 port A bits *syscon1 &= ~15; // scan entire keyboard *kbdeoi = 0xff; // clear pending interrupt *intmr2 |= 1; // enable keyboard interrupt } else { // lid closed; wake on any keyboard activity *syscon2 = syscon2_save|8; } if (timeout>0) { if (sync && timeout>1) --timeout; // we busy wait one of these seconds after wakeup *rtcmr = start+timeout; *rtceoi = 0xff; *intmr1 |= (1<<10); } // ******** actually sleep ******** *stdby = 0xAA; // ******** sleep done ******** // wait for rtc edge if (sync) { unsigned int now = *rtc; while (now==*rtc) ; } unsigned int end = *rtc; // update kernel clock struct timeval tv; gettimeofday(&tv,0); tv.tv_sec += end-start; settimeofday(&tv,0); // restore interrupts *syscon2 = syscon2_save; *intmr2 = intmr2_save; *intmr1 = intmr1_save; // turn on DAI // probably something I should do? // restore LCD *syscon1 = syscon1_save; // pddr is restored below // restore orange/yellow LED *ledflsh = 0; *pddr = pddr_save; // restore green LED *pbdr = pbdr_save; *pbddr = pbddr_save; } static void lcd_blank(void) { *pddr &= ~(1<<5); *syscon1 &= ~(1<<12); } static void lcd_restore(void) { *syscon1 |= (1<<12); *pddr |= (1<<5); } static int run_script(const char* event, const char* power, const char* lid, int idle) { int status; pid_t pid = fork(); if (pid==0) { // child char sidle[64]; if (idle<0) idle=0; sprintf(sidle,"%d",idle); execl(power_script,power_script, event,power,lid,sidle,(char*)NULL); _exit(0); // if script not found, return success } if (pid==-1) return -1; // fork failed wait(&status); syslog(LOG_INFO,"event %s %s %s %d %s", event,power,lid,idle,WEXITSTATUS(status)?"failed":"ok"); return WEXITSTATUS(status); } static const char* power_states[] = { "ac", "battery" }; static const char* lid_states[] = { "close", "open" }; static void main_loop(void) { int last_lid_state=1; // 0=closed, 1=open int last_power_state=-1; // 0=ac, 1=battery int sleep_attempt_counter=0; time_t last_time=time(0); // time of last activity while (1) { int power_state = (*pbdr>>2)&1; int lid_state = (*pbdr>>3)&1; if (last_lid_state==1) { last_time=time(0); // need to monitor keyboard somehow } if (power_state!=last_power_state) { last_power_state = power_state; run_script("power",power_states[power_state], lid_states[lid_state],time(0)-last_time); } if (lid_state!=last_lid_state) { int lcd_state = (*pddr>>5)&1; // 0=off, 1=on if (lid_state==0) { // attempt to blank if (lcd_state==0) { // lcd is already blanked last_lid_state=lid_state; last_time=time(0); sleep_attempt_counter=0; } else if (run_script("blank",power_states[power_state], lid_states[lid_state],time(0)-last_time)==0) { lcd_blank(); last_lid_state=lid_state; last_time=time(0); sleep_attempt_counter=0; } // else we don't update last_lid_state and try again later } else if (lid_state==1) { // unblank if (lcd_state==1) { // lcd is already active last_lid_state=lid_state; last_time=time(0); } else if (run_script("unblank",power_states[power_state], lid_states[lid_state],time(0)-last_time)==0) { lcd_restore(); last_lid_state=lid_state; last_time=time(0); } // else we don't update last_lid_state and try again later } } int idle = time(0)-last_time; if (lid_state==0 || idle>idle_threshold) { if (sleep_attempt_counter>0) --sleep_attempt_counter; else if (run_script("suspend",power_states[power_state], lid_states[lid_state],idle)!=0) { // try again later sleep_attempt_counter = sleep_interval/poll_interval-1; } else { // sleep now do_sleep(sleep_sync,sleep_timeout,sleep_wol); while (run_script("resume",power_states[(*pbdr>>2)&1], lid_states[(*pbdr>>3)&1],0)!=0) { sleep(wake_interval); do_sleep(sleep_sync,sleep_timeout,sleep_wol); } last_time=time(0); continue; // check new state immediately (ie. avoid poll_interval) } } sleep(poll_interval); } } int main(int argc, char **argv) { pid_t pid,sid; /* Fork off the parent process */ pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } /* If we got a good PID, then we can exit the parent process. */ if (pid > 0) { exit(EXIT_SUCCESS); } /* Change the file mode mask */ umask(0); /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { /* Log any failure here */ exit(EXIT_FAILURE); } /* Change the current working directory */ if ((chdir("/")) < 0) { /* Log any failure here */ exit(EXIT_FAILURE); } /* Open any logs here */ openlog("powerd",LOG_PID,LOG_DAEMON); /* Close out the standard file descriptors */ close(STDERR_FILENO); close(STDOUT_FILENO); close(STDIN_FILENO); /* Open standard file descriptors */ open("/dev/null",O_RDONLY); // stdin open("/dev/null",O_WRONLY); // stdout open("/dev/null",O_WRONLY); // stderr /* Daemon-specific initialization goes here */ main_loop(); return 0; }