18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * linux/drivers/parisc/power.c 38c2ecf20Sopenharmony_ci * HP PARISC soft power switch support driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2001-2007 Helge Deller <deller@gmx.de> 68c2ecf20Sopenharmony_ci * All rights reserved. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without 108c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions 118c2ecf20Sopenharmony_ci * are met: 128c2ecf20Sopenharmony_ci * 1. Redistributions of source code must retain the above copyright 138c2ecf20Sopenharmony_ci * notice, this list of conditions, and the following disclaimer, 148c2ecf20Sopenharmony_ci * without modification. 158c2ecf20Sopenharmony_ci * 2. The name of the author may not be used to endorse or promote products 168c2ecf20Sopenharmony_ci * derived from this software without specific prior written permission. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * Alternatively, this software may be distributed under the terms of the 198c2ecf20Sopenharmony_ci * GNU General Public License ("GPL"). 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 228c2ecf20Sopenharmony_ci * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 238c2ecf20Sopenharmony_ci * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 248c2ecf20Sopenharmony_ci * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 258c2ecf20Sopenharmony_ci * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 268c2ecf20Sopenharmony_ci * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 278c2ecf20Sopenharmony_ci * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 288c2ecf20Sopenharmony_ci * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 298c2ecf20Sopenharmony_ci * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * HINT: 338c2ecf20Sopenharmony_ci * Support of the soft power switch button may be enabled or disabled at 348c2ecf20Sopenharmony_ci * runtime through the "/proc/sys/kernel/power" procfs entry. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#include <linux/module.h> 388c2ecf20Sopenharmony_ci#include <linux/init.h> 398c2ecf20Sopenharmony_ci#include <linux/kernel.h> 408c2ecf20Sopenharmony_ci#include <linux/notifier.h> 418c2ecf20Sopenharmony_ci#include <linux/reboot.h> 428c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 438c2ecf20Sopenharmony_ci#include <linux/kthread.h> 448c2ecf20Sopenharmony_ci#include <linux/pm.h> 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#include <asm/pdc.h> 478c2ecf20Sopenharmony_ci#include <asm/io.h> 488c2ecf20Sopenharmony_ci#include <asm/led.h> 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#define DRIVER_NAME "powersw" 518c2ecf20Sopenharmony_ci#define KTHREAD_NAME "kpowerswd" 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* how often should the power button be polled ? */ 548c2ecf20Sopenharmony_ci#define POWERSWITCH_POLL_PER_SEC 2 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/* how long does the power button needs to be down until we react ? */ 578c2ecf20Sopenharmony_ci#define POWERSWITCH_DOWN_SEC 2 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/* assembly code to access special registers */ 608c2ecf20Sopenharmony_ci/* taken from PCXL ERS page 82 */ 618c2ecf20Sopenharmony_ci#define DIAG_CODE(code) (0x14000000 + ((code)<<5)) 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci#define MFCPU_X(rDiagReg, t_ch, t_th, code) \ 648c2ecf20Sopenharmony_ci (DIAG_CODE(code) + ((rDiagReg)<<21) + ((t_ch)<<16) + ((t_th)<<0) ) 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#define MTCPU(dr, gr) MFCPU_X(dr, gr, 0, 0x12) /* move value of gr to dr[dr] */ 678c2ecf20Sopenharmony_ci#define MFCPU_C(dr, gr) MFCPU_X(dr, gr, 0, 0x30) /* for dr0 and dr8 only ! */ 688c2ecf20Sopenharmony_ci#define MFCPU_T(dr, gr) MFCPU_X(dr, 0, gr, 0xa0) /* all dr except dr0 and dr8 */ 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#define __getDIAG(dr) ( { \ 718c2ecf20Sopenharmony_ci register unsigned long __res asm("r28");\ 728c2ecf20Sopenharmony_ci __asm__ __volatile__ ( \ 738c2ecf20Sopenharmony_ci ".word %1" : "=&r" (__res) : "i" (MFCPU_T(dr,28) ) \ 748c2ecf20Sopenharmony_ci ); \ 758c2ecf20Sopenharmony_ci __res; \ 768c2ecf20Sopenharmony_ci} ) 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* local shutdown counter */ 798c2ecf20Sopenharmony_cistatic int shutdown_timer __read_mostly; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* check, give feedback and start shutdown after one second */ 828c2ecf20Sopenharmony_cistatic void process_shutdown(void) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci if (shutdown_timer == 0) 858c2ecf20Sopenharmony_ci printk(KERN_ALERT KTHREAD_NAME ": Shutdown requested...\n"); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci shutdown_timer++; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* wait until the button was pressed for 1 second */ 908c2ecf20Sopenharmony_ci if (shutdown_timer == (POWERSWITCH_DOWN_SEC*POWERSWITCH_POLL_PER_SEC)) { 918c2ecf20Sopenharmony_ci static const char msg[] = "Shutting down..."; 928c2ecf20Sopenharmony_ci printk(KERN_INFO KTHREAD_NAME ": %s\n", msg); 938c2ecf20Sopenharmony_ci lcd_print(msg); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* send kill signal */ 968c2ecf20Sopenharmony_ci if (kill_cad_pid(SIGINT, 1)) { 978c2ecf20Sopenharmony_ci /* just in case killing init process failed */ 988c2ecf20Sopenharmony_ci machine_power_off(); 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/* main power switch task struct */ 1058c2ecf20Sopenharmony_cistatic struct task_struct *power_task; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci/* filename in /proc which can be used to enable/disable the power switch */ 1088c2ecf20Sopenharmony_ci#define SYSCTL_FILENAME "sys/kernel/power" 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci/* soft power switch enabled/disabled */ 1118c2ecf20Sopenharmony_ciint pwrsw_enabled __read_mostly = 1; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci/* main kernel thread worker. It polls the button state */ 1148c2ecf20Sopenharmony_cistatic int kpowerswd(void *param) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci __set_current_state(TASK_RUNNING); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci do { 1198c2ecf20Sopenharmony_ci int button_not_pressed; 1208c2ecf20Sopenharmony_ci unsigned long soft_power_reg = (unsigned long) param; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci schedule_timeout_interruptible(pwrsw_enabled ? HZ : HZ/POWERSWITCH_POLL_PER_SEC); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (unlikely(!pwrsw_enabled)) 1258c2ecf20Sopenharmony_ci continue; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci if (soft_power_reg) { 1288c2ecf20Sopenharmony_ci /* 1298c2ecf20Sopenharmony_ci * Non-Gecko-style machines: 1308c2ecf20Sopenharmony_ci * Check the power switch status which is read from the 1318c2ecf20Sopenharmony_ci * real I/O location at soft_power_reg. 1328c2ecf20Sopenharmony_ci * Bit 31 ("the lowest bit) is the status of the power switch. 1338c2ecf20Sopenharmony_ci * This bit is "1" if the button is NOT pressed. 1348c2ecf20Sopenharmony_ci */ 1358c2ecf20Sopenharmony_ci button_not_pressed = (gsc_readl(soft_power_reg) & 0x1); 1368c2ecf20Sopenharmony_ci } else { 1378c2ecf20Sopenharmony_ci /* 1388c2ecf20Sopenharmony_ci * On gecko style machines (e.g. 712/xx and 715/xx) 1398c2ecf20Sopenharmony_ci * the power switch status is stored in Bit 0 ("the highest bit") 1408c2ecf20Sopenharmony_ci * of CPU diagnose register 25. 1418c2ecf20Sopenharmony_ci * Warning: Some machines never reset the DIAG flag, even if 1428c2ecf20Sopenharmony_ci * the button has been released again. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci button_not_pressed = (__getDIAG(25) & 0x80000000); 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (likely(button_not_pressed)) { 1488c2ecf20Sopenharmony_ci if (unlikely(shutdown_timer && /* avoid writing if not necessary */ 1498c2ecf20Sopenharmony_ci shutdown_timer < (POWERSWITCH_DOWN_SEC*POWERSWITCH_POLL_PER_SEC))) { 1508c2ecf20Sopenharmony_ci shutdown_timer = 0; 1518c2ecf20Sopenharmony_ci printk(KERN_INFO KTHREAD_NAME ": Shutdown request aborted.\n"); 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci } else 1548c2ecf20Sopenharmony_ci process_shutdown(); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci } while (!kthread_should_stop()); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return 0; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci/* 1648c2ecf20Sopenharmony_ci * powerfail interruption handler (irq IRQ_FROM_REGION(CPU_IRQ_REGION)+2) 1658c2ecf20Sopenharmony_ci */ 1668c2ecf20Sopenharmony_ci#if 0 1678c2ecf20Sopenharmony_cistatic void powerfail_interrupt(int code, void *x) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci printk(KERN_CRIT "POWERFAIL INTERRUPTION !\n"); 1708c2ecf20Sopenharmony_ci poweroff(); 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci#endif 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci/* parisc_panic_event() is called by the panic handler. 1788c2ecf20Sopenharmony_ci * As soon as a panic occurs, our tasklets above will not be 1798c2ecf20Sopenharmony_ci * executed any longer. This function then re-enables the 1808c2ecf20Sopenharmony_ci * soft-power switch and allows the user to switch off the system 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_cistatic int parisc_panic_event(struct notifier_block *this, 1838c2ecf20Sopenharmony_ci unsigned long event, void *ptr) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci /* re-enable the soft-power switch */ 1868c2ecf20Sopenharmony_ci pdc_soft_power_button(0); 1878c2ecf20Sopenharmony_ci return NOTIFY_DONE; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic struct notifier_block parisc_panic_block = { 1918c2ecf20Sopenharmony_ci .notifier_call = parisc_panic_event, 1928c2ecf20Sopenharmony_ci .priority = INT_MAX, 1938c2ecf20Sopenharmony_ci}; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic int __init power_init(void) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci unsigned long ret; 1998c2ecf20Sopenharmony_ci unsigned long soft_power_reg; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci#if 0 2028c2ecf20Sopenharmony_ci request_irq( IRQ_FROM_REGION(CPU_IRQ_REGION)+2, &powerfail_interrupt, 2038c2ecf20Sopenharmony_ci 0, "powerfail", NULL); 2048c2ecf20Sopenharmony_ci#endif 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* enable the soft power switch if possible */ 2078c2ecf20Sopenharmony_ci ret = pdc_soft_power_info(&soft_power_reg); 2088c2ecf20Sopenharmony_ci if (ret == PDC_OK) 2098c2ecf20Sopenharmony_ci ret = pdc_soft_power_button(1); 2108c2ecf20Sopenharmony_ci if (ret != PDC_OK) 2118c2ecf20Sopenharmony_ci soft_power_reg = -1UL; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci switch (soft_power_reg) { 2148c2ecf20Sopenharmony_ci case 0: printk(KERN_INFO DRIVER_NAME ": Gecko-style soft power switch enabled.\n"); 2158c2ecf20Sopenharmony_ci break; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci case -1UL: printk(KERN_INFO DRIVER_NAME ": Soft power switch support not available.\n"); 2188c2ecf20Sopenharmony_ci return -ENODEV; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci default: printk(KERN_INFO DRIVER_NAME ": Soft power switch at 0x%08lx enabled.\n", 2218c2ecf20Sopenharmony_ci soft_power_reg); 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci power_task = kthread_run(kpowerswd, (void*)soft_power_reg, KTHREAD_NAME); 2258c2ecf20Sopenharmony_ci if (IS_ERR(power_task)) { 2268c2ecf20Sopenharmony_ci printk(KERN_ERR DRIVER_NAME ": thread creation failed. Driver not loaded.\n"); 2278c2ecf20Sopenharmony_ci pdc_soft_power_button(0); 2288c2ecf20Sopenharmony_ci return -EIO; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci /* Register a call for panic conditions. */ 2328c2ecf20Sopenharmony_ci atomic_notifier_chain_register(&panic_notifier_list, 2338c2ecf20Sopenharmony_ci &parisc_panic_block); 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci return 0; 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic void __exit power_exit(void) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci kthread_stop(power_task); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci atomic_notifier_chain_unregister(&panic_notifier_list, 2438c2ecf20Sopenharmony_ci &parisc_panic_block); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci pdc_soft_power_button(0); 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ciarch_initcall(power_init); 2498c2ecf20Sopenharmony_cimodule_exit(power_exit); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ciMODULE_AUTHOR("Helge Deller <deller@gmx.de>"); 2538c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Soft power switch driver"); 2548c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 255