1d0173278SGuenter Roeck // SPDX-License-Identifier: GPL-2.0+ 2b7e04f8cSWim Van Sebroeck /* 3b7e04f8cSWim Van Sebroeck * NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x 4b7e04f8cSWim Van Sebroeck * 5b7e04f8cSWim Van Sebroeck * This code is based on wdt.c with original copyright. 6b7e04f8cSWim Van Sebroeck * 7b7e04f8cSWim Van Sebroeck * (C) Copyright 2006 Sven Anders, <anders@anduras.de> 8b7e04f8cSWim Van Sebroeck * and Marcus Junker, <junker@anduras.de> 9b7e04f8cSWim Van Sebroeck * 10b7e04f8cSWim Van Sebroeck * Neither Sven Anders, Marcus Junker nor ANDURAS AG 11b7e04f8cSWim Van Sebroeck * admit liability nor provide warranty for any of this software. 12b7e04f8cSWim Van Sebroeck * This material is provided "AS-IS" and at no charge. 13b7e04f8cSWim Van Sebroeck * 14b7e04f8cSWim Van Sebroeck * Release 1.1 15b7e04f8cSWim Van Sebroeck */ 16b7e04f8cSWim Van Sebroeck 1727c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1827c766aaSJoe Perches 19b7e04f8cSWim Van Sebroeck #include <linux/module.h> 20b7e04f8cSWim Van Sebroeck #include <linux/types.h> 21b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h> 22b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h> 23b7e04f8cSWim Van Sebroeck #include <linux/ioport.h> 24b7e04f8cSWim Van Sebroeck #include <linux/delay.h> 25b7e04f8cSWim Van Sebroeck #include <linux/notifier.h> 26b7e04f8cSWim Van Sebroeck #include <linux/fs.h> 27b7e04f8cSWim Van Sebroeck #include <linux/reboot.h> 28b7e04f8cSWim Van Sebroeck #include <linux/init.h> 29b7e04f8cSWim Van Sebroeck #include <linux/spinlock.h> 30b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h> 31aee334c2SAlan Cox #include <linux/io.h> 32aee334c2SAlan Cox #include <linux/uaccess.h> 33b7e04f8cSWim Van Sebroeck 34b7e04f8cSWim Van Sebroeck 35b7e04f8cSWim Van Sebroeck /* #define DEBUG 1 */ 36b7e04f8cSWim Van Sebroeck 37b7e04f8cSWim Van Sebroeck #define DEFAULT_TIMEOUT 1 /* 1 minute */ 38b7e04f8cSWim Van Sebroeck #define MAX_TIMEOUT 255 39b7e04f8cSWim Van Sebroeck 40b7e04f8cSWim Van Sebroeck #define VERSION "1.1" 41b7e04f8cSWim Van Sebroeck #define MODNAME "pc87413 WDT" 42b7e04f8cSWim Van Sebroeck #define DPFX MODNAME " - DEBUG: " 43b7e04f8cSWim Van Sebroeck 44b7e04f8cSWim Van Sebroeck #define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */ 45b7e04f8cSWim Van Sebroeck #define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1) 46b7e04f8cSWim Van Sebroeck #define SWC_LDN 0x04 47b7e04f8cSWim Van Sebroeck #define SIOCFG2 0x22 /* Serial IO register */ 4825985edcSLucas De Marchi #define WDCTL 0x10 /* Watchdog-Timer-Control-Register */ 49b7e04f8cSWim Van Sebroeck #define WDTO 0x11 /* Watchdog timeout register */ 50b7e04f8cSWim Van Sebroeck #define WDCFG 0x12 /* Watchdog config register */ 51b7e04f8cSWim Van Sebroeck 5276550d32SRandy Dunlap #define IO_DEFAULT 0x2E /* Address used on Portwell Boards */ 5376550d32SRandy Dunlap 5476550d32SRandy Dunlap static int io = IO_DEFAULT; 557ccdb946SJonathan McDowell static int swc_base_addr = -1; 56b7e04f8cSWim Van Sebroeck 57b7e04f8cSWim Van Sebroeck static int timeout = DEFAULT_TIMEOUT; /* timeout value */ 58aee334c2SAlan Cox static unsigned long timer_enabled; /* is the timer enabled? */ 59b7e04f8cSWim Van Sebroeck 60b7e04f8cSWim Van Sebroeck static char expect_close; /* is the close expected? */ 61b7e04f8cSWim Van Sebroeck 62aee334c2SAlan Cox static DEFINE_SPINLOCK(io_lock); /* to guard us from io races */ 63b7e04f8cSWim Van Sebroeck 6486a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 65b7e04f8cSWim Van Sebroeck 66b7e04f8cSWim Van Sebroeck /* -- Low level function ----------------------------------------*/ 67b7e04f8cSWim Van Sebroeck 68b7e04f8cSWim Van Sebroeck /* Select pins for Watchdog output */ 69b7e04f8cSWim Van Sebroeck 70b7e04f8cSWim Van Sebroeck static inline void pc87413_select_wdt_out(void) 71b7e04f8cSWim Van Sebroeck { 72b7e04f8cSWim Van Sebroeck unsigned int cr_data = 0; 73b7e04f8cSWim Van Sebroeck 74b7e04f8cSWim Van Sebroeck /* Step 1: Select multiple pin,pin55,as WDT output */ 75b7e04f8cSWim Van Sebroeck 76b7e04f8cSWim Van Sebroeck outb_p(SIOCFG2, WDT_INDEX_IO_PORT); 77b7e04f8cSWim Van Sebroeck 78b7e04f8cSWim Van Sebroeck cr_data = inb(WDT_DATA_IO_PORT); 79b7e04f8cSWim Van Sebroeck 80b7e04f8cSWim Van Sebroeck cr_data |= 0x80; /* Set Bit7 to 1*/ 81b7e04f8cSWim Van Sebroeck outb_p(SIOCFG2, WDT_INDEX_IO_PORT); 82b7e04f8cSWim Van Sebroeck 83b7e04f8cSWim Van Sebroeck outb_p(cr_data, WDT_DATA_IO_PORT); 84b7e04f8cSWim Van Sebroeck 85b7e04f8cSWim Van Sebroeck #ifdef DEBUG 8627c766aaSJoe Perches pr_info(DPFX 87aee334c2SAlan Cox "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n", 88aee334c2SAlan Cox cr_data); 89b7e04f8cSWim Van Sebroeck #endif 90b7e04f8cSWim Van Sebroeck } 91b7e04f8cSWim Van Sebroeck 92b7e04f8cSWim Van Sebroeck /* Enable SWC functions */ 93b7e04f8cSWim Van Sebroeck 94b7e04f8cSWim Van Sebroeck static inline void pc87413_enable_swc(void) 95b7e04f8cSWim Van Sebroeck { 96b7e04f8cSWim Van Sebroeck unsigned int cr_data = 0; 97b7e04f8cSWim Van Sebroeck 98b7e04f8cSWim Van Sebroeck /* Step 2: Enable SWC functions */ 99b7e04f8cSWim Van Sebroeck 100b7e04f8cSWim Van Sebroeck outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */ 101b7e04f8cSWim Van Sebroeck outb_p(SWC_LDN, WDT_DATA_IO_PORT); 102b7e04f8cSWim Van Sebroeck 103b7e04f8cSWim Van Sebroeck outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */ 104b7e04f8cSWim Van Sebroeck cr_data = inb(WDT_DATA_IO_PORT); 105b7e04f8cSWim Van Sebroeck cr_data |= 0x01; /* Set Bit0 to 1 */ 106b7e04f8cSWim Van Sebroeck outb_p(0x30, WDT_INDEX_IO_PORT); 107b7e04f8cSWim Van Sebroeck outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ 108b7e04f8cSWim Van Sebroeck 109b7e04f8cSWim Van Sebroeck #ifdef DEBUG 11027c766aaSJoe Perches pr_info(DPFX "pc87413 - Enable SWC functions\n"); 111b7e04f8cSWim Van Sebroeck #endif 112b7e04f8cSWim Van Sebroeck } 113b7e04f8cSWim Van Sebroeck 114b7e04f8cSWim Van Sebroeck /* Read SWC I/O base address */ 115b7e04f8cSWim Van Sebroeck 1167ccdb946SJonathan McDowell static void pc87413_get_swc_base_addr(void) 117b7e04f8cSWim Van Sebroeck { 118b7e04f8cSWim Van Sebroeck unsigned char addr_l, addr_h = 0; 119b7e04f8cSWim Van Sebroeck 120b7e04f8cSWim Van Sebroeck /* Step 3: Read SWC I/O Base Address */ 121b7e04f8cSWim Van Sebroeck 122b7e04f8cSWim Van Sebroeck outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */ 123b7e04f8cSWim Van Sebroeck addr_h = inb(WDT_DATA_IO_PORT); 124b7e04f8cSWim Van Sebroeck 125b7e04f8cSWim Van Sebroeck outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */ 126b7e04f8cSWim Van Sebroeck 127b7e04f8cSWim Van Sebroeck addr_l = inb(WDT_DATA_IO_PORT); 128b7e04f8cSWim Van Sebroeck 129b7e04f8cSWim Van Sebroeck swc_base_addr = (addr_h << 8) + addr_l; 130b7e04f8cSWim Van Sebroeck #ifdef DEBUG 13127c766aaSJoe Perches pr_info(DPFX 132aee334c2SAlan Cox "Read SWC I/O Base Address: low %d, high %d, res %d\n", 133aee334c2SAlan Cox addr_l, addr_h, swc_base_addr); 134b7e04f8cSWim Van Sebroeck #endif 135b7e04f8cSWim Van Sebroeck } 136b7e04f8cSWim Van Sebroeck 137b7e04f8cSWim Van Sebroeck /* Select Bank 3 of SWC */ 138b7e04f8cSWim Van Sebroeck 1397ccdb946SJonathan McDowell static inline void pc87413_swc_bank3(void) 140b7e04f8cSWim Van Sebroeck { 141b7e04f8cSWim Van Sebroeck /* Step 4: Select Bank3 of SWC */ 142b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f); 143b7e04f8cSWim Van Sebroeck #ifdef DEBUG 14427c766aaSJoe Perches pr_info(DPFX "Select Bank3 of SWC\n"); 145b7e04f8cSWim Van Sebroeck #endif 146b7e04f8cSWim Van Sebroeck } 147b7e04f8cSWim Van Sebroeck 148b7e04f8cSWim Van Sebroeck /* Set watchdog timeout to x minutes */ 149b7e04f8cSWim Van Sebroeck 1507ccdb946SJonathan McDowell static inline void pc87413_programm_wdto(char pc87413_time) 151b7e04f8cSWim Van Sebroeck { 152b7e04f8cSWim Van Sebroeck /* Step 5: Programm WDTO, Twd. */ 153b7e04f8cSWim Van Sebroeck outb_p(pc87413_time, swc_base_addr + WDTO); 154b7e04f8cSWim Van Sebroeck #ifdef DEBUG 15527c766aaSJoe Perches pr_info(DPFX "Set WDTO to %d minutes\n", pc87413_time); 156b7e04f8cSWim Van Sebroeck #endif 157b7e04f8cSWim Van Sebroeck } 158b7e04f8cSWim Van Sebroeck 159b7e04f8cSWim Van Sebroeck /* Enable WDEN */ 160b7e04f8cSWim Van Sebroeck 1617ccdb946SJonathan McDowell static inline void pc87413_enable_wden(void) 162b7e04f8cSWim Van Sebroeck { 163b7e04f8cSWim Van Sebroeck /* Step 6: Enable WDEN */ 164b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); 165b7e04f8cSWim Van Sebroeck #ifdef DEBUG 16627c766aaSJoe Perches pr_info(DPFX "Enable WDEN\n"); 167b7e04f8cSWim Van Sebroeck #endif 168b7e04f8cSWim Van Sebroeck } 169b7e04f8cSWim Van Sebroeck 170b7e04f8cSWim Van Sebroeck /* Enable SW_WD_TREN */ 1717ccdb946SJonathan McDowell static inline void pc87413_enable_sw_wd_tren(void) 172b7e04f8cSWim Van Sebroeck { 173b7e04f8cSWim Van Sebroeck /* Enable SW_WD_TREN */ 174b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG); 175b7e04f8cSWim Van Sebroeck #ifdef DEBUG 17627c766aaSJoe Perches pr_info(DPFX "Enable SW_WD_TREN\n"); 177b7e04f8cSWim Van Sebroeck #endif 178b7e04f8cSWim Van Sebroeck } 179b7e04f8cSWim Van Sebroeck 180b7e04f8cSWim Van Sebroeck /* Disable SW_WD_TREN */ 181b7e04f8cSWim Van Sebroeck 1827ccdb946SJonathan McDowell static inline void pc87413_disable_sw_wd_tren(void) 183b7e04f8cSWim Van Sebroeck { 184b7e04f8cSWim Van Sebroeck /* Disable SW_WD_TREN */ 185b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG); 186b7e04f8cSWim Van Sebroeck #ifdef DEBUG 18727c766aaSJoe Perches pr_info(DPFX "pc87413 - Disable SW_WD_TREN\n"); 188b7e04f8cSWim Van Sebroeck #endif 189b7e04f8cSWim Van Sebroeck } 190b7e04f8cSWim Van Sebroeck 191b7e04f8cSWim Van Sebroeck /* Enable SW_WD_TRG */ 192b7e04f8cSWim Van Sebroeck 1937ccdb946SJonathan McDowell static inline void pc87413_enable_sw_wd_trg(void) 194b7e04f8cSWim Van Sebroeck { 195b7e04f8cSWim Van Sebroeck /* Enable SW_WD_TRG */ 196b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL); 197b7e04f8cSWim Van Sebroeck #ifdef DEBUG 19827c766aaSJoe Perches pr_info(DPFX "pc87413 - Enable SW_WD_TRG\n"); 199b7e04f8cSWim Van Sebroeck #endif 200b7e04f8cSWim Van Sebroeck } 201b7e04f8cSWim Van Sebroeck 202b7e04f8cSWim Van Sebroeck /* Disable SW_WD_TRG */ 203b7e04f8cSWim Van Sebroeck 2047ccdb946SJonathan McDowell static inline void pc87413_disable_sw_wd_trg(void) 205b7e04f8cSWim Van Sebroeck { 206b7e04f8cSWim Van Sebroeck /* Disable SW_WD_TRG */ 207b7e04f8cSWim Van Sebroeck outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL); 208b7e04f8cSWim Van Sebroeck #ifdef DEBUG 20927c766aaSJoe Perches pr_info(DPFX "Disable SW_WD_TRG\n"); 210b7e04f8cSWim Van Sebroeck #endif 211b7e04f8cSWim Van Sebroeck } 212b7e04f8cSWim Van Sebroeck 213b7e04f8cSWim Van Sebroeck /* -- Higher level functions ------------------------------------*/ 214b7e04f8cSWim Van Sebroeck 215b7e04f8cSWim Van Sebroeck /* Enable the watchdog */ 216b7e04f8cSWim Van Sebroeck 217b7e04f8cSWim Van Sebroeck static void pc87413_enable(void) 218b7e04f8cSWim Van Sebroeck { 219b7e04f8cSWim Van Sebroeck spin_lock(&io_lock); 220b7e04f8cSWim Van Sebroeck 2217ccdb946SJonathan McDowell pc87413_swc_bank3(); 2227ccdb946SJonathan McDowell pc87413_programm_wdto(timeout); 2237ccdb946SJonathan McDowell pc87413_enable_wden(); 2247ccdb946SJonathan McDowell pc87413_enable_sw_wd_tren(); 2257ccdb946SJonathan McDowell pc87413_enable_sw_wd_trg(); 226b7e04f8cSWim Van Sebroeck 227b7e04f8cSWim Van Sebroeck spin_unlock(&io_lock); 228b7e04f8cSWim Van Sebroeck } 229b7e04f8cSWim Van Sebroeck 230b7e04f8cSWim Van Sebroeck /* Disable the watchdog */ 231b7e04f8cSWim Van Sebroeck 232b7e04f8cSWim Van Sebroeck static void pc87413_disable(void) 233b7e04f8cSWim Van Sebroeck { 234b7e04f8cSWim Van Sebroeck spin_lock(&io_lock); 235b7e04f8cSWim Van Sebroeck 2367ccdb946SJonathan McDowell pc87413_swc_bank3(); 2377ccdb946SJonathan McDowell pc87413_disable_sw_wd_tren(); 2387ccdb946SJonathan McDowell pc87413_disable_sw_wd_trg(); 2397ccdb946SJonathan McDowell pc87413_programm_wdto(0); 240b7e04f8cSWim Van Sebroeck 241b7e04f8cSWim Van Sebroeck spin_unlock(&io_lock); 242b7e04f8cSWim Van Sebroeck } 243b7e04f8cSWim Van Sebroeck 244b7e04f8cSWim Van Sebroeck /* Refresh the watchdog */ 245b7e04f8cSWim Van Sebroeck 246b7e04f8cSWim Van Sebroeck static void pc87413_refresh(void) 247b7e04f8cSWim Van Sebroeck { 248b7e04f8cSWim Van Sebroeck spin_lock(&io_lock); 249b7e04f8cSWim Van Sebroeck 2507ccdb946SJonathan McDowell pc87413_swc_bank3(); 2517ccdb946SJonathan McDowell pc87413_disable_sw_wd_tren(); 2527ccdb946SJonathan McDowell pc87413_disable_sw_wd_trg(); 2537ccdb946SJonathan McDowell pc87413_programm_wdto(timeout); 2547ccdb946SJonathan McDowell pc87413_enable_wden(); 2557ccdb946SJonathan McDowell pc87413_enable_sw_wd_tren(); 2567ccdb946SJonathan McDowell pc87413_enable_sw_wd_trg(); 257b7e04f8cSWim Van Sebroeck 258b7e04f8cSWim Van Sebroeck spin_unlock(&io_lock); 259b7e04f8cSWim Van Sebroeck } 260b7e04f8cSWim Van Sebroeck 261b7e04f8cSWim Van Sebroeck /* -- File operations -------------------------------------------*/ 262b7e04f8cSWim Van Sebroeck 263b7e04f8cSWim Van Sebroeck /** 264b7e04f8cSWim Van Sebroeck * pc87413_open: 265b7e04f8cSWim Van Sebroeck * @inode: inode of device 266b7e04f8cSWim Van Sebroeck * @file: file handle to device 267b7e04f8cSWim Van Sebroeck * 268b7e04f8cSWim Van Sebroeck */ 269b7e04f8cSWim Van Sebroeck 270b7e04f8cSWim Van Sebroeck static int pc87413_open(struct inode *inode, struct file *file) 271b7e04f8cSWim Van Sebroeck { 272b7e04f8cSWim Van Sebroeck /* /dev/watchdog can only be opened once */ 273b7e04f8cSWim Van Sebroeck 274b7e04f8cSWim Van Sebroeck if (test_and_set_bit(0, &timer_enabled)) 275b7e04f8cSWim Van Sebroeck return -EBUSY; 276b7e04f8cSWim Van Sebroeck 277b7e04f8cSWim Van Sebroeck if (nowayout) 278b7e04f8cSWim Van Sebroeck __module_get(THIS_MODULE); 279b7e04f8cSWim Van Sebroeck 280b7e04f8cSWim Van Sebroeck /* Reload and activate timer */ 281b7e04f8cSWim Van Sebroeck pc87413_refresh(); 282b7e04f8cSWim Van Sebroeck 28327c766aaSJoe Perches pr_info("Watchdog enabled. Timeout set to %d minute(s).\n", timeout); 284b7e04f8cSWim Van Sebroeck 285c5bf68feSKirill Smelkov return stream_open(inode, file); 286b7e04f8cSWim Van Sebroeck } 287b7e04f8cSWim Van Sebroeck 288b7e04f8cSWim Van Sebroeck /** 289b7e04f8cSWim Van Sebroeck * pc87413_release: 290b7e04f8cSWim Van Sebroeck * @inode: inode to board 291b7e04f8cSWim Van Sebroeck * @file: file handle to board 292b7e04f8cSWim Van Sebroeck * 293b7e04f8cSWim Van Sebroeck * The watchdog has a configurable API. There is a religious dispute 294b7e04f8cSWim Van Sebroeck * between people who want their watchdog to be able to shut down and 295b7e04f8cSWim Van Sebroeck * those who want to be sure if the watchdog manager dies the machine 296b7e04f8cSWim Van Sebroeck * reboots. In the former case we disable the counters, in the latter 297b7e04f8cSWim Van Sebroeck * case you have to open it again very soon. 298b7e04f8cSWim Van Sebroeck */ 299b7e04f8cSWim Van Sebroeck 300b7e04f8cSWim Van Sebroeck static int pc87413_release(struct inode *inode, struct file *file) 301b7e04f8cSWim Van Sebroeck { 302b7e04f8cSWim Van Sebroeck /* Shut off the timer. */ 303b7e04f8cSWim Van Sebroeck 304b7e04f8cSWim Van Sebroeck if (expect_close == 42) { 305b7e04f8cSWim Van Sebroeck pc87413_disable(); 30627c766aaSJoe Perches pr_info("Watchdog disabled, sleeping again...\n"); 307b7e04f8cSWim Van Sebroeck } else { 30827c766aaSJoe Perches pr_crit("Unexpected close, not stopping watchdog!\n"); 309b7e04f8cSWim Van Sebroeck pc87413_refresh(); 310b7e04f8cSWim Van Sebroeck } 311b7e04f8cSWim Van Sebroeck clear_bit(0, &timer_enabled); 312b7e04f8cSWim Van Sebroeck expect_close = 0; 313b7e04f8cSWim Van Sebroeck return 0; 314b7e04f8cSWim Van Sebroeck } 315b7e04f8cSWim Van Sebroeck 316b7e04f8cSWim Van Sebroeck /** 317b7e04f8cSWim Van Sebroeck * pc87413_status: 318b7e04f8cSWim Van Sebroeck * 319b7e04f8cSWim Van Sebroeck * return, if the watchdog is enabled (timeout is set...) 320b7e04f8cSWim Van Sebroeck */ 321b7e04f8cSWim Van Sebroeck 322b7e04f8cSWim Van Sebroeck 323b7e04f8cSWim Van Sebroeck static int pc87413_status(void) 324b7e04f8cSWim Van Sebroeck { 325b7e04f8cSWim Van Sebroeck return 0; /* currently not supported */ 326b7e04f8cSWim Van Sebroeck } 327b7e04f8cSWim Van Sebroeck 328b7e04f8cSWim Van Sebroeck /** 329b7e04f8cSWim Van Sebroeck * pc87413_write: 330b7e04f8cSWim Van Sebroeck * @file: file handle to the watchdog 331b7e04f8cSWim Van Sebroeck * @data: data buffer to write 332b7e04f8cSWim Van Sebroeck * @len: length in bytes 333b7e04f8cSWim Van Sebroeck * @ppos: pointer to the position to write. No seeks allowed 334b7e04f8cSWim Van Sebroeck * 335b7e04f8cSWim Van Sebroeck * A write to a watchdog device is defined as a keepalive signal. Any 336b7e04f8cSWim Van Sebroeck * write of data will do, as we we don't define content meaning. 337b7e04f8cSWim Van Sebroeck */ 338b7e04f8cSWim Van Sebroeck 339b7e04f8cSWim Van Sebroeck static ssize_t pc87413_write(struct file *file, const char __user *data, 340b7e04f8cSWim Van Sebroeck size_t len, loff_t *ppos) 341b7e04f8cSWim Van Sebroeck { 342b7e04f8cSWim Van Sebroeck /* See if we got the magic character 'V' and reload the timer */ 343b7e04f8cSWim Van Sebroeck if (len) { 344b7e04f8cSWim Van Sebroeck if (!nowayout) { 345b7e04f8cSWim Van Sebroeck size_t i; 346b7e04f8cSWim Van Sebroeck 347b7e04f8cSWim Van Sebroeck /* reset expect flag */ 348b7e04f8cSWim Van Sebroeck expect_close = 0; 349b7e04f8cSWim Van Sebroeck 350aee334c2SAlan Cox /* scan to see whether or not we got the 351aee334c2SAlan Cox magic character */ 352b7e04f8cSWim Van Sebroeck for (i = 0; i != len; i++) { 353b7e04f8cSWim Van Sebroeck char c; 354b7e04f8cSWim Van Sebroeck if (get_user(c, data + i)) 355b7e04f8cSWim Van Sebroeck return -EFAULT; 356b7e04f8cSWim Van Sebroeck if (c == 'V') 357b7e04f8cSWim Van Sebroeck expect_close = 42; 358b7e04f8cSWim Van Sebroeck } 359b7e04f8cSWim Van Sebroeck } 360b7e04f8cSWim Van Sebroeck 361b7e04f8cSWim Van Sebroeck /* someone wrote to us, we should reload the timer */ 362b7e04f8cSWim Van Sebroeck pc87413_refresh(); 363b7e04f8cSWim Van Sebroeck } 364b7e04f8cSWim Van Sebroeck return len; 365b7e04f8cSWim Van Sebroeck } 366b7e04f8cSWim Van Sebroeck 367b7e04f8cSWim Van Sebroeck /** 368b7e04f8cSWim Van Sebroeck * pc87413_ioctl: 369b7e04f8cSWim Van Sebroeck * @file: file handle to the device 370b7e04f8cSWim Van Sebroeck * @cmd: watchdog command 371b7e04f8cSWim Van Sebroeck * @arg: argument pointer 372b7e04f8cSWim Van Sebroeck * 373b7e04f8cSWim Van Sebroeck * The watchdog API defines a common set of functions for all watchdogs 374b7e04f8cSWim Van Sebroeck * according to their available features. We only actually usefully support 375b7e04f8cSWim Van Sebroeck * querying capabilities and current status. 376b7e04f8cSWim Van Sebroeck */ 377b7e04f8cSWim Van Sebroeck 378aee334c2SAlan Cox static long pc87413_ioctl(struct file *file, unsigned int cmd, 379aee334c2SAlan Cox unsigned long arg) 380b7e04f8cSWim Van Sebroeck { 381b7e04f8cSWim Van Sebroeck int new_timeout; 382b7e04f8cSWim Van Sebroeck 383b7e04f8cSWim Van Sebroeck union { 384b7e04f8cSWim Van Sebroeck struct watchdog_info __user *ident; 385b7e04f8cSWim Van Sebroeck int __user *i; 386b7e04f8cSWim Van Sebroeck } uarg; 387b7e04f8cSWim Van Sebroeck 38842747d71SWim Van Sebroeck static const struct watchdog_info ident = { 389b7e04f8cSWim Van Sebroeck .options = WDIOF_KEEPALIVEPING | 390b7e04f8cSWim Van Sebroeck WDIOF_SETTIMEOUT | 391b7e04f8cSWim Van Sebroeck WDIOF_MAGICCLOSE, 392b7e04f8cSWim Van Sebroeck .firmware_version = 1, 3937944d3a5SWim Van Sebroeck .identity = "PC87413(HF/F) watchdog", 394b7e04f8cSWim Van Sebroeck }; 395b7e04f8cSWim Van Sebroeck 396b7e04f8cSWim Van Sebroeck uarg.i = (int __user *)arg; 397b7e04f8cSWim Van Sebroeck 398b7e04f8cSWim Van Sebroeck switch (cmd) { 399b7e04f8cSWim Van Sebroeck case WDIOC_GETSUPPORT: 400b7e04f8cSWim Van Sebroeck return copy_to_user(uarg.ident, &ident, 401b7e04f8cSWim Van Sebroeck sizeof(ident)) ? -EFAULT : 0; 402b7e04f8cSWim Van Sebroeck case WDIOC_GETSTATUS: 403b7e04f8cSWim Van Sebroeck return put_user(pc87413_status(), uarg.i); 404b7e04f8cSWim Van Sebroeck case WDIOC_GETBOOTSTATUS: 405b7e04f8cSWim Van Sebroeck return put_user(0, uarg.i); 4060c06090cSWim Van Sebroeck case WDIOC_SETOPTIONS: 4070c06090cSWim Van Sebroeck { 4080c06090cSWim Van Sebroeck int options, retval = -EINVAL; 4090c06090cSWim Van Sebroeck if (get_user(options, uarg.i)) 4100c06090cSWim Van Sebroeck return -EFAULT; 4110c06090cSWim Van Sebroeck if (options & WDIOS_DISABLECARD) { 4120c06090cSWim Van Sebroeck pc87413_disable(); 4130c06090cSWim Van Sebroeck retval = 0; 4140c06090cSWim Van Sebroeck } 4150c06090cSWim Van Sebroeck if (options & WDIOS_ENABLECARD) { 4160c06090cSWim Van Sebroeck pc87413_enable(); 4170c06090cSWim Van Sebroeck retval = 0; 4180c06090cSWim Van Sebroeck } 4190c06090cSWim Van Sebroeck return retval; 4200c06090cSWim Van Sebroeck } 421b7e04f8cSWim Van Sebroeck case WDIOC_KEEPALIVE: 422b7e04f8cSWim Van Sebroeck pc87413_refresh(); 423b7e04f8cSWim Van Sebroeck #ifdef DEBUG 42427c766aaSJoe Perches pr_info(DPFX "keepalive\n"); 425b7e04f8cSWim Van Sebroeck #endif 426b7e04f8cSWim Van Sebroeck return 0; 427b7e04f8cSWim Van Sebroeck case WDIOC_SETTIMEOUT: 428b7e04f8cSWim Van Sebroeck if (get_user(new_timeout, uarg.i)) 429b7e04f8cSWim Van Sebroeck return -EFAULT; 430aee334c2SAlan Cox /* the API states this is given in secs */ 431b7e04f8cSWim Van Sebroeck new_timeout /= 60; 432b7e04f8cSWim Van Sebroeck if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) 433b7e04f8cSWim Van Sebroeck return -EINVAL; 434b7e04f8cSWim Van Sebroeck timeout = new_timeout; 435b7e04f8cSWim Van Sebroeck pc87413_refresh(); 4368baee572SGustavo A. R. Silva /* fall through - and return the new timeout... */ 437b7e04f8cSWim Van Sebroeck case WDIOC_GETTIMEOUT: 438b7e04f8cSWim Van Sebroeck new_timeout = timeout * 60; 439b7e04f8cSWim Van Sebroeck return put_user(new_timeout, uarg.i); 440aee334c2SAlan Cox default: 441aee334c2SAlan Cox return -ENOTTY; 442b7e04f8cSWim Van Sebroeck } 443b7e04f8cSWim Van Sebroeck } 444b7e04f8cSWim Van Sebroeck 445b7e04f8cSWim Van Sebroeck /* -- Notifier funtions -----------------------------------------*/ 446b7e04f8cSWim Van Sebroeck 447b7e04f8cSWim Van Sebroeck /** 448b7e04f8cSWim Van Sebroeck * notify_sys: 449b7e04f8cSWim Van Sebroeck * @this: our notifier block 450b7e04f8cSWim Van Sebroeck * @code: the event being reported 451b7e04f8cSWim Van Sebroeck * @unused: unused 452b7e04f8cSWim Van Sebroeck * 453b7e04f8cSWim Van Sebroeck * Our notifier is called on system shutdowns. We want to turn the card 454b7e04f8cSWim Van Sebroeck * off at reboot otherwise the machine will reboot again during memory 455b7e04f8cSWim Van Sebroeck * test or worse yet during the following fsck. This would suck, in fact 456b7e04f8cSWim Van Sebroeck * trust me - if it happens it does suck. 457b7e04f8cSWim Van Sebroeck */ 458b7e04f8cSWim Van Sebroeck 459b7e04f8cSWim Van Sebroeck static int pc87413_notify_sys(struct notifier_block *this, 460b7e04f8cSWim Van Sebroeck unsigned long code, 461b7e04f8cSWim Van Sebroeck void *unused) 462b7e04f8cSWim Van Sebroeck { 463b7e04f8cSWim Van Sebroeck if (code == SYS_DOWN || code == SYS_HALT) 464b7e04f8cSWim Van Sebroeck /* Turn the card off */ 465b7e04f8cSWim Van Sebroeck pc87413_disable(); 466b7e04f8cSWim Van Sebroeck return NOTIFY_DONE; 467b7e04f8cSWim Van Sebroeck } 468b7e04f8cSWim Van Sebroeck 469b7e04f8cSWim Van Sebroeck /* -- Module's structures ---------------------------------------*/ 470b7e04f8cSWim Van Sebroeck 471b7e04f8cSWim Van Sebroeck static const struct file_operations pc87413_fops = { 472b7e04f8cSWim Van Sebroeck .owner = THIS_MODULE, 473b7e04f8cSWim Van Sebroeck .llseek = no_llseek, 474b7e04f8cSWim Van Sebroeck .write = pc87413_write, 475aee334c2SAlan Cox .unlocked_ioctl = pc87413_ioctl, 476b6dfb247SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 477b7e04f8cSWim Van Sebroeck .open = pc87413_open, 478b7e04f8cSWim Van Sebroeck .release = pc87413_release, 479b7e04f8cSWim Van Sebroeck }; 480b7e04f8cSWim Van Sebroeck 481aee334c2SAlan Cox static struct notifier_block pc87413_notifier = { 482b7e04f8cSWim Van Sebroeck .notifier_call = pc87413_notify_sys, 483b7e04f8cSWim Van Sebroeck }; 484b7e04f8cSWim Van Sebroeck 485aee334c2SAlan Cox static struct miscdevice pc87413_miscdev = { 486b7e04f8cSWim Van Sebroeck .minor = WATCHDOG_MINOR, 487b7e04f8cSWim Van Sebroeck .name = "watchdog", 4887944d3a5SWim Van Sebroeck .fops = &pc87413_fops, 489b7e04f8cSWim Van Sebroeck }; 490b7e04f8cSWim Van Sebroeck 491b7e04f8cSWim Van Sebroeck /* -- Module init functions -------------------------------------*/ 492b7e04f8cSWim Van Sebroeck 493b7e04f8cSWim Van Sebroeck /** 494b7e04f8cSWim Van Sebroeck * pc87413_init: module's "constructor" 495b7e04f8cSWim Van Sebroeck * 496b7e04f8cSWim Van Sebroeck * Set up the WDT watchdog board. All we have to do is grab the 497b7e04f8cSWim Van Sebroeck * resources we require and bitch if anyone beat us to them. 498b7e04f8cSWim Van Sebroeck * The open() function will actually kick the board off. 499b7e04f8cSWim Van Sebroeck */ 500b7e04f8cSWim Van Sebroeck 501b7e04f8cSWim Van Sebroeck static int __init pc87413_init(void) 502b7e04f8cSWim Van Sebroeck { 503b7e04f8cSWim Van Sebroeck int ret; 504b7e04f8cSWim Van Sebroeck 50527c766aaSJoe Perches pr_info("Version " VERSION " at io 0x%X\n", 506aee334c2SAlan Cox WDT_INDEX_IO_PORT); 507b7e04f8cSWim Van Sebroeck 5087ccdb946SJonathan McDowell if (!request_muxed_region(io, 2, MODNAME)) 5097ccdb946SJonathan McDowell return -EBUSY; 510b7e04f8cSWim Van Sebroeck 511b7e04f8cSWim Van Sebroeck ret = register_reboot_notifier(&pc87413_notifier); 5125f5e1909SJingoo Han if (ret != 0) 51327c766aaSJoe Perches pr_err("cannot register reboot notifier (err=%d)\n", ret); 514b7e04f8cSWim Van Sebroeck 515b7e04f8cSWim Van Sebroeck ret = misc_register(&pc87413_miscdev); 516b7e04f8cSWim Van Sebroeck if (ret != 0) { 51727c766aaSJoe Perches pr_err("cannot register miscdev on minor=%d (err=%d)\n", 518b7e04f8cSWim Van Sebroeck WATCHDOG_MINOR, ret); 5197ccdb946SJonathan McDowell goto reboot_unreg; 520b7e04f8cSWim Van Sebroeck } 52127c766aaSJoe Perches pr_info("initialized. timeout=%d min\n", timeout); 5227ccdb946SJonathan McDowell 5237ccdb946SJonathan McDowell pc87413_select_wdt_out(); 5247ccdb946SJonathan McDowell pc87413_enable_swc(); 5257ccdb946SJonathan McDowell pc87413_get_swc_base_addr(); 5267ccdb946SJonathan McDowell 5277ccdb946SJonathan McDowell if (!request_region(swc_base_addr, 0x20, MODNAME)) { 52827c766aaSJoe Perches pr_err("cannot request SWC region at 0x%x\n", swc_base_addr); 5297ccdb946SJonathan McDowell ret = -EBUSY; 5307ccdb946SJonathan McDowell goto misc_unreg; 5317ccdb946SJonathan McDowell } 5327ccdb946SJonathan McDowell 533b7e04f8cSWim Van Sebroeck pc87413_enable(); 5347ccdb946SJonathan McDowell 5357ccdb946SJonathan McDowell release_region(io, 2); 536b7e04f8cSWim Van Sebroeck return 0; 5377ccdb946SJonathan McDowell 5387ccdb946SJonathan McDowell misc_unreg: 5397ccdb946SJonathan McDowell misc_deregister(&pc87413_miscdev); 5407ccdb946SJonathan McDowell reboot_unreg: 5417ccdb946SJonathan McDowell unregister_reboot_notifier(&pc87413_notifier); 5427ccdb946SJonathan McDowell release_region(io, 2); 5437ccdb946SJonathan McDowell return ret; 544b7e04f8cSWim Van Sebroeck } 545b7e04f8cSWim Van Sebroeck 546b7e04f8cSWim Van Sebroeck /** 547b7e04f8cSWim Van Sebroeck * pc87413_exit: module's "destructor" 548b7e04f8cSWim Van Sebroeck * 549b7e04f8cSWim Van Sebroeck * Unload the watchdog. You cannot do this with any file handles open. 550b7e04f8cSWim Van Sebroeck * If your watchdog is set to continue ticking on close and you unload 551b7e04f8cSWim Van Sebroeck * it, well it keeps ticking. We won't get the interrupt but the board 552b7e04f8cSWim Van Sebroeck * will not touch PC memory so all is fine. You just have to load a new 553b7e04f8cSWim Van Sebroeck * module in 60 seconds or reboot. 554b7e04f8cSWim Van Sebroeck */ 555b7e04f8cSWim Van Sebroeck 556b7e04f8cSWim Van Sebroeck static void __exit pc87413_exit(void) 557b7e04f8cSWim Van Sebroeck { 558b7e04f8cSWim Van Sebroeck /* Stop the timer before we leave */ 559aee334c2SAlan Cox if (!nowayout) { 560b7e04f8cSWim Van Sebroeck pc87413_disable(); 56127c766aaSJoe Perches pr_info("Watchdog disabled\n"); 562b7e04f8cSWim Van Sebroeck } 563b7e04f8cSWim Van Sebroeck 564b7e04f8cSWim Van Sebroeck misc_deregister(&pc87413_miscdev); 565b7e04f8cSWim Van Sebroeck unregister_reboot_notifier(&pc87413_notifier); 5667ccdb946SJonathan McDowell release_region(swc_base_addr, 0x20); 567b7e04f8cSWim Van Sebroeck 56827c766aaSJoe Perches pr_info("watchdog component driver removed\n"); 569b7e04f8cSWim Van Sebroeck } 570b7e04f8cSWim Van Sebroeck 571b7e04f8cSWim Van Sebroeck module_init(pc87413_init); 572b7e04f8cSWim Van Sebroeck module_exit(pc87413_exit); 573b7e04f8cSWim Van Sebroeck 5745f5e1909SJingoo Han MODULE_AUTHOR("Sven Anders <anders@anduras.de>"); 5755f5e1909SJingoo Han MODULE_AUTHOR("Marcus Junker <junker@anduras.de>"); 576b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("PC87413 WDT driver"); 577b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL"); 578b7e04f8cSWim Van Sebroeck 5795d1c93ceSDavid Howells module_param_hw(io, int, ioport, 0); 58076550d32SRandy Dunlap MODULE_PARM_DESC(io, MODNAME " I/O port (default: " 58176550d32SRandy Dunlap __MODULE_STRING(IO_DEFAULT) ")."); 582b7e04f8cSWim Van Sebroeck 583b7e04f8cSWim Van Sebroeck module_param(timeout, int, 0); 584aee334c2SAlan Cox MODULE_PARM_DESC(timeout, 585aee334c2SAlan Cox "Watchdog timeout in minutes (default=" 58676550d32SRandy Dunlap __MODULE_STRING(DEFAULT_TIMEOUT) ")."); 587b7e04f8cSWim Van Sebroeck 58886a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 589aee334c2SAlan Cox MODULE_PARM_DESC(nowayout, 590aee334c2SAlan Cox "Watchdog cannot be stopped once started (default=" 591aee334c2SAlan Cox __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 592b7e04f8cSWim Van Sebroeck 593