/* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2001 Keith M Wesolowski * Copyright (C) 2001 Paul Mundt * Copyright (C) 2003 Guido Guenther <agx@sigxcpu.org> */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/notifier.h> #include <linux/delay.h> #include <linux/ds17287rtc.h> #include <linux/interrupt.h> #include <linux/pm.h> #include <asm/addrspace.h> #include <asm/irq.h> #include <asm/reboot.h> #include <asm/wbflush.h> #include <asm/ip32/mace.h> #include <asm/ip32/crime.h> #include <asm/ip32/ip32_ints.h> #define POWERDOWN_TIMEOUT 120 /* * Blink frequency during reboot grace period and when panicked. */ #define POWERDOWN_FREQ (HZ / 4) #define PANIC_FREQ (HZ / 8) static struct timer_list power_timer, blink_timer, debounce_timer; static int has_panicked, shuting_down; static void ip32_machine_restart(char *command) __attribute__((noreturn)); static void ip32_machine_halt(void) __attribute__((noreturn)); static void ip32_machine_power_off(void) __attribute__((noreturn)); static void ip32_machine_restart(char *cmd) { crime->control = CRIME_CONTROL_HARD_RESET; while (1); } static inline void ip32_machine_halt(void) { ip32_machine_power_off(); } static void ip32_machine_power_off(void) { unsigned char reg_a, xctrl_a, xctrl_b; disable_irq(MACEISA_RTC_IRQ); reg_a = CMOS_READ(RTC_REG_A); /* setup for kickstart & wake-up (DS12287 Ref. Man. p. 19) */ reg_a &= ~DS_REGA_DV2; reg_a |= DS_REGA_DV1; CMOS_WRITE(reg_a | DS_REGA_DV0, RTC_REG_A); wbflush(); xctrl_b = CMOS_READ(DS_B1_XCTRL4B) | DS_XCTRL4B_ABE | DS_XCTRL4B_KFE; CMOS_WRITE(xctrl_b, DS_B1_XCTRL4B); xctrl_a = CMOS_READ(DS_B1_XCTRL4A) & ~DS_XCTRL4A_IFS; CMOS_WRITE(xctrl_a, DS_B1_XCTRL4A); wbflush(); /* adios amigos... */ CMOS_WRITE(xctrl_a | DS_XCTRL4A_PAB, DS_B1_XCTRL4A); CMOS_WRITE(reg_a, RTC_REG_A); wbflush(); while (1); } static void power_timeout(unsigned long data) { ip32_machine_power_off(); } static void blink_timeout(unsigned long data) { unsigned long led = mace->perif.ctrl.misc ^ MACEISA_LED_RED; mace->perif.ctrl.misc = led; mod_timer(&blink_timer, jiffies + data); } static void debounce(unsigned long data) { unsigned char reg_a, reg_c, xctrl_a; reg_c = CMOS_READ(RTC_INTR_FLAGS); reg_a = CMOS_READ(RTC_REG_A); CMOS_WRITE(reg_a | DS_REGA_DV0, RTC_REG_A); wbflush(); xctrl_a = CMOS_READ(DS_B1_XCTRL4A); if ((xctrl_a & DS_XCTRL4A_IFS) || (reg_c & RTC_IRQF )) { /* Interrupt still being sent. */ debounce_timer.expires = jiffies + 50; add_timer(&debounce_timer); /* clear interrupt source */ CMOS_WRITE(xctrl_a & ~DS_XCTRL4A_IFS, DS_B1_XCTRL4A); CMOS_WRITE(reg_a & ~DS_REGA_DV0, RTC_REG_A); return; } CMOS_WRITE(reg_a & ~DS_REGA_DV0, RTC_REG_A); if (has_panicked) ip32_machine_restart(NULL); enable_irq(MACEISA_RTC_IRQ); } static inline void ip32_power_button(void) { if (has_panicked) return; if (shuting_down || kill_cad_pid(SIGINT, 1)) { /* No init process or button pressed twice. */ ip32_machine_power_off(); } shuting_down = 1; blink_timer.data = POWERDOWN_FREQ; blink_timeout(POWERDOWN_FREQ); init_timer(&power_timer); power_timer.function = power_timeout; power_timer.expires = jiffies + POWERDOWN_TIMEOUT * HZ; add_timer(&power_timer); } static irqreturn_t ip32_rtc_int(int irq, void *dev_id) { unsigned char reg_c; reg_c = CMOS_READ(RTC_INTR_FLAGS); if (!(reg_c & RTC_IRQF)) { printk(KERN_WARNING "%s: RTC IRQ without RTC_IRQF\n", __func__); } /* Wait until interrupt goes away */ disable_irq_nosync(MACEISA_RTC_IRQ); init_timer(&debounce_timer); debounce_timer.function = debounce; debounce_timer.expires = jiffies + 50; add_timer(&debounce_timer); printk(KERN_DEBUG "Power button pressed\n"); ip32_power_button(); return IRQ_HANDLED; } static int panic_event(struct notifier_block *this, unsigned long event, void *ptr) { unsigned long led; if (has_panicked) return NOTIFY_DONE; has_panicked = 1; /* turn off the green LED */ led = mace->perif.ctrl.misc | MACEISA_LED_GREEN; mace->perif.ctrl.misc = led; blink_timer.data = PANIC_FREQ; blink_timeout(PANIC_FREQ); return NOTIFY_DONE; } static struct notifier_block panic_block = { .notifier_call = panic_event, }; static __init int ip32_reboot_setup(void) { /* turn on the green led only */ unsigned long led = mace->perif.ctrl.misc; led |= MACEISA_LED_RED; led &= ~MACEISA_LED_GREEN; mace->perif.ctrl.misc = led; _machine_restart = ip32_machine_restart; _machine_halt = ip32_machine_halt; pm_power_off = ip32_machine_power_off; init_timer(&blink_timer); blink_timer.function = blink_timeout; atomic_notifier_chain_register(&panic_notifier_list, &panic_block); if (request_irq(MACEISA_RTC_IRQ, ip32_rtc_int, 0, "rtc", NULL)) panic("Can't allocate MACEISA RTC IRQ"); return 0; } subsys_initcall(ip32_reboot_setup);