Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  *      NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x
0004  *
0005  *      This code is based on wdt.c with original copyright.
0006  *
0007  *      (C) Copyright 2006 Sven Anders, <anders@anduras.de>
0008  *                     and Marcus Junker, <junker@anduras.de>
0009  *
0010  *      Neither Sven Anders, Marcus Junker nor ANDURAS AG
0011  *      admit liability nor provide warranty for any of this software.
0012  *      This material is provided "AS-IS" and at no charge.
0013  *
0014  *      Release 1.1
0015  */
0016 
0017 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0018 
0019 #include <linux/module.h>
0020 #include <linux/types.h>
0021 #include <linux/miscdevice.h>
0022 #include <linux/watchdog.h>
0023 #include <linux/ioport.h>
0024 #include <linux/delay.h>
0025 #include <linux/notifier.h>
0026 #include <linux/fs.h>
0027 #include <linux/reboot.h>
0028 #include <linux/init.h>
0029 #include <linux/spinlock.h>
0030 #include <linux/moduleparam.h>
0031 #include <linux/io.h>
0032 #include <linux/uaccess.h>
0033 
0034 
0035 /* #define DEBUG 1 */
0036 
0037 #define DEFAULT_TIMEOUT     1       /* 1 minute */
0038 #define MAX_TIMEOUT         255
0039 
0040 #define VERSION             "1.1"
0041 #define MODNAME             "pc87413 WDT"
0042 #define DPFX                MODNAME " - DEBUG: "
0043 
0044 #define WDT_INDEX_IO_PORT   (io+0)  /* I/O port base (index register) */
0045 #define WDT_DATA_IO_PORT    (WDT_INDEX_IO_PORT+1)
0046 #define SWC_LDN             0x04
0047 #define SIOCFG2             0x22    /* Serial IO register */
0048 #define WDCTL               0x10    /* Watchdog-Timer-Control-Register */
0049 #define WDTO                0x11    /* Watchdog timeout register */
0050 #define WDCFG               0x12    /* Watchdog config register */
0051 
0052 #define IO_DEFAULT  0x2E        /* Address used on Portwell Boards */
0053 
0054 static int io = IO_DEFAULT;
0055 static int swc_base_addr = -1;
0056 
0057 static int timeout = DEFAULT_TIMEOUT;   /* timeout value */
0058 static unsigned long timer_enabled; /* is the timer enabled? */
0059 
0060 static char expect_close;       /* is the close expected? */
0061 
0062 static DEFINE_SPINLOCK(io_lock);    /* to guard us from io races */
0063 
0064 static bool nowayout = WATCHDOG_NOWAYOUT;
0065 
0066 /* -- Low level function ----------------------------------------*/
0067 
0068 /* Select pins for Watchdog output */
0069 
0070 static inline void pc87413_select_wdt_out(void)
0071 {
0072     unsigned int cr_data = 0;
0073 
0074     /* Step 1: Select multiple pin,pin55,as WDT output */
0075 
0076     outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
0077 
0078     cr_data = inb(WDT_DATA_IO_PORT);
0079 
0080     cr_data |= 0x80; /* Set Bit7 to 1*/
0081     outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
0082 
0083     outb_p(cr_data, WDT_DATA_IO_PORT);
0084 
0085 #ifdef DEBUG
0086     pr_info(DPFX
0087         "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n",
0088                                 cr_data);
0089 #endif
0090 }
0091 
0092 /* Enable SWC functions */
0093 
0094 static inline void pc87413_enable_swc(void)
0095 {
0096     unsigned int cr_data = 0;
0097 
0098     /* Step 2: Enable SWC functions */
0099 
0100     outb_p(0x07, WDT_INDEX_IO_PORT);    /* Point SWC_LDN (LDN=4) */
0101     outb_p(SWC_LDN, WDT_DATA_IO_PORT);
0102 
0103     outb_p(0x30, WDT_INDEX_IO_PORT);    /* Read Index 0x30 First */
0104     cr_data = inb(WDT_DATA_IO_PORT);
0105     cr_data |= 0x01;            /* Set Bit0 to 1 */
0106     outb_p(0x30, WDT_INDEX_IO_PORT);
0107     outb_p(cr_data, WDT_DATA_IO_PORT);  /* Index0x30_bit0P1 */
0108 
0109 #ifdef DEBUG
0110     pr_info(DPFX "pc87413 - Enable SWC functions\n");
0111 #endif
0112 }
0113 
0114 /* Read SWC I/O base address */
0115 
0116 static void pc87413_get_swc_base_addr(void)
0117 {
0118     unsigned char addr_l, addr_h = 0;
0119 
0120     /* Step 3: Read SWC I/O Base Address */
0121 
0122     outb_p(0x60, WDT_INDEX_IO_PORT);    /* Read Index 0x60 */
0123     addr_h = inb(WDT_DATA_IO_PORT);
0124 
0125     outb_p(0x61, WDT_INDEX_IO_PORT);    /* Read Index 0x61 */
0126 
0127     addr_l = inb(WDT_DATA_IO_PORT);
0128 
0129     swc_base_addr = (addr_h << 8) + addr_l;
0130 #ifdef DEBUG
0131     pr_info(DPFX
0132         "Read SWC I/O Base Address: low %d, high %d, res %d\n",
0133                         addr_l, addr_h, swc_base_addr);
0134 #endif
0135 }
0136 
0137 /* Select Bank 3 of SWC */
0138 
0139 static inline void pc87413_swc_bank3(void)
0140 {
0141     /* Step 4: Select Bank3 of SWC */
0142     outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f);
0143 #ifdef DEBUG
0144     pr_info(DPFX "Select Bank3 of SWC\n");
0145 #endif
0146 }
0147 
0148 /* Set watchdog timeout to x minutes */
0149 
0150 static inline void pc87413_programm_wdto(char pc87413_time)
0151 {
0152     /* Step 5: Programm WDTO, Twd. */
0153     outb_p(pc87413_time, swc_base_addr + WDTO);
0154 #ifdef DEBUG
0155     pr_info(DPFX "Set WDTO to %d minutes\n", pc87413_time);
0156 #endif
0157 }
0158 
0159 /* Enable WDEN */
0160 
0161 static inline void pc87413_enable_wden(void)
0162 {
0163     /* Step 6: Enable WDEN */
0164     outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL);
0165 #ifdef DEBUG
0166     pr_info(DPFX "Enable WDEN\n");
0167 #endif
0168 }
0169 
0170 /* Enable SW_WD_TREN */
0171 static inline void pc87413_enable_sw_wd_tren(void)
0172 {
0173     /* Enable SW_WD_TREN */
0174     outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG);
0175 #ifdef DEBUG
0176     pr_info(DPFX "Enable SW_WD_TREN\n");
0177 #endif
0178 }
0179 
0180 /* Disable SW_WD_TREN */
0181 
0182 static inline void pc87413_disable_sw_wd_tren(void)
0183 {
0184     /* Disable SW_WD_TREN */
0185     outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG);
0186 #ifdef DEBUG
0187     pr_info(DPFX "pc87413 - Disable SW_WD_TREN\n");
0188 #endif
0189 }
0190 
0191 /* Enable SW_WD_TRG */
0192 
0193 static inline void pc87413_enable_sw_wd_trg(void)
0194 {
0195     /* Enable SW_WD_TRG */
0196     outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL);
0197 #ifdef DEBUG
0198     pr_info(DPFX "pc87413 - Enable SW_WD_TRG\n");
0199 #endif
0200 }
0201 
0202 /* Disable SW_WD_TRG */
0203 
0204 static inline void pc87413_disable_sw_wd_trg(void)
0205 {
0206     /* Disable SW_WD_TRG */
0207     outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL);
0208 #ifdef DEBUG
0209     pr_info(DPFX "Disable SW_WD_TRG\n");
0210 #endif
0211 }
0212 
0213 /* -- Higher level functions ------------------------------------*/
0214 
0215 /* Enable the watchdog */
0216 
0217 static void pc87413_enable(void)
0218 {
0219     spin_lock(&io_lock);
0220 
0221     pc87413_swc_bank3();
0222     pc87413_programm_wdto(timeout);
0223     pc87413_enable_wden();
0224     pc87413_enable_sw_wd_tren();
0225     pc87413_enable_sw_wd_trg();
0226 
0227     spin_unlock(&io_lock);
0228 }
0229 
0230 /* Disable the watchdog */
0231 
0232 static void pc87413_disable(void)
0233 {
0234     spin_lock(&io_lock);
0235 
0236     pc87413_swc_bank3();
0237     pc87413_disable_sw_wd_tren();
0238     pc87413_disable_sw_wd_trg();
0239     pc87413_programm_wdto(0);
0240 
0241     spin_unlock(&io_lock);
0242 }
0243 
0244 /* Refresh the watchdog */
0245 
0246 static void pc87413_refresh(void)
0247 {
0248     spin_lock(&io_lock);
0249 
0250     pc87413_swc_bank3();
0251     pc87413_disable_sw_wd_tren();
0252     pc87413_disable_sw_wd_trg();
0253     pc87413_programm_wdto(timeout);
0254     pc87413_enable_wden();
0255     pc87413_enable_sw_wd_tren();
0256     pc87413_enable_sw_wd_trg();
0257 
0258     spin_unlock(&io_lock);
0259 }
0260 
0261 /* -- File operations -------------------------------------------*/
0262 
0263 /**
0264  *  pc87413_open:
0265  *  @inode: inode of device
0266  *  @file: file handle to device
0267  *
0268  */
0269 
0270 static int pc87413_open(struct inode *inode, struct file *file)
0271 {
0272     /* /dev/watchdog can only be opened once */
0273 
0274     if (test_and_set_bit(0, &timer_enabled))
0275         return -EBUSY;
0276 
0277     if (nowayout)
0278         __module_get(THIS_MODULE);
0279 
0280     /* Reload and activate timer */
0281     pc87413_refresh();
0282 
0283     pr_info("Watchdog enabled. Timeout set to %d minute(s).\n", timeout);
0284 
0285     return stream_open(inode, file);
0286 }
0287 
0288 /**
0289  *  pc87413_release:
0290  *  @inode: inode to board
0291  *  @file: file handle to board
0292  *
0293  *  The watchdog has a configurable API. There is a religious dispute
0294  *  between people who want their watchdog to be able to shut down and
0295  *  those who want to be sure if the watchdog manager dies the machine
0296  *  reboots. In the former case we disable the counters, in the latter
0297  *  case you have to open it again very soon.
0298  */
0299 
0300 static int pc87413_release(struct inode *inode, struct file *file)
0301 {
0302     /* Shut off the timer. */
0303 
0304     if (expect_close == 42) {
0305         pc87413_disable();
0306         pr_info("Watchdog disabled, sleeping again...\n");
0307     } else {
0308         pr_crit("Unexpected close, not stopping watchdog!\n");
0309         pc87413_refresh();
0310     }
0311     clear_bit(0, &timer_enabled);
0312     expect_close = 0;
0313     return 0;
0314 }
0315 
0316 /**
0317  *  pc87413_status:
0318  *
0319  *      return, if the watchdog is enabled (timeout is set...)
0320  */
0321 
0322 
0323 static int pc87413_status(void)
0324 {
0325       return 0; /* currently not supported */
0326 }
0327 
0328 /**
0329  *  pc87413_write:
0330  *  @file: file handle to the watchdog
0331  *  @data: data buffer to write
0332  *  @len: length in bytes
0333  *  @ppos: pointer to the position to write. No seeks allowed
0334  *
0335  *  A write to a watchdog device is defined as a keepalive signal. Any
0336  *  write of data will do, as we we don't define content meaning.
0337  */
0338 
0339 static ssize_t pc87413_write(struct file *file, const char __user *data,
0340                  size_t len, loff_t *ppos)
0341 {
0342     /* See if we got the magic character 'V' and reload the timer */
0343     if (len) {
0344         if (!nowayout) {
0345             size_t i;
0346 
0347             /* reset expect flag */
0348             expect_close = 0;
0349 
0350             /* scan to see whether or not we got the
0351                magic character */
0352             for (i = 0; i != len; i++) {
0353                 char c;
0354                 if (get_user(c, data + i))
0355                     return -EFAULT;
0356                 if (c == 'V')
0357                     expect_close = 42;
0358             }
0359         }
0360 
0361         /* someone wrote to us, we should reload the timer */
0362         pc87413_refresh();
0363     }
0364     return len;
0365 }
0366 
0367 /**
0368  *  pc87413_ioctl:
0369  *  @file: file handle to the device
0370  *  @cmd: watchdog command
0371  *  @arg: argument pointer
0372  *
0373  *  The watchdog API defines a common set of functions for all watchdogs
0374  *  according to their available features. We only actually usefully support
0375  *  querying capabilities and current status.
0376  */
0377 
0378 static long pc87413_ioctl(struct file *file, unsigned int cmd,
0379                         unsigned long arg)
0380 {
0381     int new_timeout;
0382 
0383     union {
0384         struct watchdog_info __user *ident;
0385         int __user *i;
0386     } uarg;
0387 
0388     static const struct watchdog_info ident = {
0389         .options          = WDIOF_KEEPALIVEPING |
0390                     WDIOF_SETTIMEOUT |
0391                     WDIOF_MAGICCLOSE,
0392         .firmware_version = 1,
0393         .identity         = "PC87413(HF/F) watchdog",
0394     };
0395 
0396     uarg.i = (int __user *)arg;
0397 
0398     switch (cmd) {
0399     case WDIOC_GETSUPPORT:
0400         return copy_to_user(uarg.ident, &ident,
0401                     sizeof(ident)) ? -EFAULT : 0;
0402     case WDIOC_GETSTATUS:
0403         return put_user(pc87413_status(), uarg.i);
0404     case WDIOC_GETBOOTSTATUS:
0405         return put_user(0, uarg.i);
0406     case WDIOC_SETOPTIONS:
0407     {
0408         int options, retval = -EINVAL;
0409         if (get_user(options, uarg.i))
0410             return -EFAULT;
0411         if (options & WDIOS_DISABLECARD) {
0412             pc87413_disable();
0413             retval = 0;
0414         }
0415         if (options & WDIOS_ENABLECARD) {
0416             pc87413_enable();
0417             retval = 0;
0418         }
0419         return retval;
0420     }
0421     case WDIOC_KEEPALIVE:
0422         pc87413_refresh();
0423 #ifdef DEBUG
0424         pr_info(DPFX "keepalive\n");
0425 #endif
0426         return 0;
0427     case WDIOC_SETTIMEOUT:
0428         if (get_user(new_timeout, uarg.i))
0429             return -EFAULT;
0430         /* the API states this is given in secs */
0431         new_timeout /= 60;
0432         if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
0433             return -EINVAL;
0434         timeout = new_timeout;
0435         pc87413_refresh();
0436         fallthrough;    /* and return the new timeout */
0437     case WDIOC_GETTIMEOUT:
0438         new_timeout = timeout * 60;
0439         return put_user(new_timeout, uarg.i);
0440     default:
0441         return -ENOTTY;
0442     }
0443 }
0444 
0445 /* -- Notifier functions -----------------------------------------*/
0446 
0447 /**
0448  *  pc87413_notify_sys:
0449  *  @this: our notifier block
0450  *  @code: the event being reported
0451  *  @unused: unused
0452  *
0453  *  Our notifier is called on system shutdowns. We want to turn the card
0454  *  off at reboot otherwise the machine will reboot again during memory
0455  *  test or worse yet during the following fsck. This would suck, in fact
0456  *  trust me - if it happens it does suck.
0457  */
0458 
0459 static int pc87413_notify_sys(struct notifier_block *this,
0460                   unsigned long code,
0461                   void *unused)
0462 {
0463     if (code == SYS_DOWN || code == SYS_HALT)
0464         /* Turn the card off */
0465         pc87413_disable();
0466     return NOTIFY_DONE;
0467 }
0468 
0469 /* -- Module's structures ---------------------------------------*/
0470 
0471 static const struct file_operations pc87413_fops = {
0472     .owner      = THIS_MODULE,
0473     .llseek     = no_llseek,
0474     .write      = pc87413_write,
0475     .unlocked_ioctl = pc87413_ioctl,
0476     .compat_ioctl   = compat_ptr_ioctl,
0477     .open       = pc87413_open,
0478     .release    = pc87413_release,
0479 };
0480 
0481 static struct notifier_block pc87413_notifier = {
0482     .notifier_call  = pc87413_notify_sys,
0483 };
0484 
0485 static struct miscdevice pc87413_miscdev = {
0486     .minor          = WATCHDOG_MINOR,
0487     .name           = "watchdog",
0488     .fops           = &pc87413_fops,
0489 };
0490 
0491 /* -- Module init functions -------------------------------------*/
0492 
0493 /**
0494  *  pc87413_init: module's "constructor"
0495  *
0496  *  Set up the WDT watchdog board. All we have to do is grab the
0497  *  resources we require and bitch if anyone beat us to them.
0498  *  The open() function will actually kick the board off.
0499  */
0500 
0501 static int __init pc87413_init(void)
0502 {
0503     int ret;
0504 
0505     pr_info("Version " VERSION " at io 0x%X\n",
0506                             WDT_INDEX_IO_PORT);
0507 
0508     if (!request_muxed_region(io, 2, MODNAME))
0509         return -EBUSY;
0510 
0511     ret = register_reboot_notifier(&pc87413_notifier);
0512     if (ret != 0)
0513         pr_err("cannot register reboot notifier (err=%d)\n", ret);
0514 
0515     ret = misc_register(&pc87413_miscdev);
0516     if (ret != 0) {
0517         pr_err("cannot register miscdev on minor=%d (err=%d)\n",
0518                WATCHDOG_MINOR, ret);
0519         goto reboot_unreg;
0520     }
0521     pr_info("initialized. timeout=%d min\n", timeout);
0522 
0523     pc87413_select_wdt_out();
0524     pc87413_enable_swc();
0525     pc87413_get_swc_base_addr();
0526 
0527     if (!request_region(swc_base_addr, 0x20, MODNAME)) {
0528         pr_err("cannot request SWC region at 0x%x\n", swc_base_addr);
0529         ret = -EBUSY;
0530         goto misc_unreg;
0531     }
0532 
0533     pc87413_enable();
0534 
0535     release_region(io, 2);
0536     return 0;
0537 
0538 misc_unreg:
0539     misc_deregister(&pc87413_miscdev);
0540 reboot_unreg:
0541     unregister_reboot_notifier(&pc87413_notifier);
0542     release_region(io, 2);
0543     return ret;
0544 }
0545 
0546 /**
0547  *  pc87413_exit: module's "destructor"
0548  *
0549  *  Unload the watchdog. You cannot do this with any file handles open.
0550  *  If your watchdog is set to continue ticking on close and you unload
0551  *  it, well it keeps ticking. We won't get the interrupt but the board
0552  *  will not touch PC memory so all is fine. You just have to load a new
0553  *  module in 60 seconds or reboot.
0554  */
0555 
0556 static void __exit pc87413_exit(void)
0557 {
0558     /* Stop the timer before we leave */
0559     if (!nowayout) {
0560         pc87413_disable();
0561         pr_info("Watchdog disabled\n");
0562     }
0563 
0564     misc_deregister(&pc87413_miscdev);
0565     unregister_reboot_notifier(&pc87413_notifier);
0566     release_region(swc_base_addr, 0x20);
0567 
0568     pr_info("watchdog component driver removed\n");
0569 }
0570 
0571 module_init(pc87413_init);
0572 module_exit(pc87413_exit);
0573 
0574 MODULE_AUTHOR("Sven Anders <anders@anduras.de>");
0575 MODULE_AUTHOR("Marcus Junker <junker@anduras.de>");
0576 MODULE_DESCRIPTION("PC87413 WDT driver");
0577 MODULE_LICENSE("GPL");
0578 
0579 module_param_hw(io, int, ioport, 0);
0580 MODULE_PARM_DESC(io, MODNAME " I/O port (default: "
0581                     __MODULE_STRING(IO_DEFAULT) ").");
0582 
0583 module_param(timeout, int, 0);
0584 MODULE_PARM_DESC(timeout,
0585         "Watchdog timeout in minutes (default="
0586                 __MODULE_STRING(DEFAULT_TIMEOUT) ").");
0587 
0588 module_param(nowayout, bool, 0);
0589 MODULE_PARM_DESC(nowayout,
0590         "Watchdog cannot be stopped once started (default="
0591                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0592