282 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * RDC321x watchdog driver4 *5 * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>6 *7 * This driver is highly inspired from the cpu5_wdt driver8 */9 10#include <linux/module.h>11#include <linux/moduleparam.h>12#include <linux/types.h>13#include <linux/errno.h>14#include <linux/miscdevice.h>15#include <linux/fs.h>16#include <linux/ioport.h>17#include <linux/timer.h>18#include <linux/completion.h>19#include <linux/jiffies.h>20#include <linux/platform_device.h>21#include <linux/watchdog.h>22#include <linux/io.h>23#include <linux/uaccess.h>24#include <linux/mfd/rdc321x.h>25 26#define RDC_WDT_MASK 0x80000000 /* Mask */27#define RDC_WDT_EN 0x00800000 /* Enable bit */28#define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */29#define RDC_WDT_RST 0x00100000 /* Reset bit */30#define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */31#define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */32#define RDC_WDT_CNT 0x00000001 /* WDT count */33 34#define RDC_CLS_TMR 0x80003844 /* Clear timer */35 36#define RDC_WDT_INTERVAL (HZ/10+1)37 38static int ticks = 1000;39 40/* some device data */41 42static struct {43 struct completion stop;44 int running;45 struct timer_list timer;46 int queue;47 int default_ticks;48 unsigned long inuse;49 spinlock_t lock;50 struct pci_dev *sb_pdev;51 int base_reg;52} rdc321x_wdt_device;53 54/* generic helper functions */55 56static void rdc321x_wdt_trigger(struct timer_list *unused)57{58 unsigned long flags;59 u32 val;60 61 if (rdc321x_wdt_device.running)62 ticks--;63 64 /* keep watchdog alive */65 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);66 pci_read_config_dword(rdc321x_wdt_device.sb_pdev,67 rdc321x_wdt_device.base_reg, &val);68 val |= RDC_WDT_EN;69 pci_write_config_dword(rdc321x_wdt_device.sb_pdev,70 rdc321x_wdt_device.base_reg, val);71 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);72 73 /* requeue?? */74 if (rdc321x_wdt_device.queue && ticks)75 mod_timer(&rdc321x_wdt_device.timer,76 jiffies + RDC_WDT_INTERVAL);77 else {78 /* ticks doesn't matter anyway */79 complete(&rdc321x_wdt_device.stop);80 }81 82}83 84static void rdc321x_wdt_reset(void)85{86 ticks = rdc321x_wdt_device.default_ticks;87}88 89static void rdc321x_wdt_start(void)90{91 unsigned long flags;92 93 if (!rdc321x_wdt_device.queue) {94 rdc321x_wdt_device.queue = 1;95 96 /* Clear the timer */97 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);98 pci_write_config_dword(rdc321x_wdt_device.sb_pdev,99 rdc321x_wdt_device.base_reg, RDC_CLS_TMR);100 101 /* Enable watchdog and set the timeout to 81.92 us */102 pci_write_config_dword(rdc321x_wdt_device.sb_pdev,103 rdc321x_wdt_device.base_reg,104 RDC_WDT_EN | RDC_WDT_CNT);105 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);106 107 mod_timer(&rdc321x_wdt_device.timer,108 jiffies + RDC_WDT_INTERVAL);109 }110 111 /* if process dies, counter is not decremented */112 rdc321x_wdt_device.running++;113}114 115static int rdc321x_wdt_stop(void)116{117 if (rdc321x_wdt_device.running)118 rdc321x_wdt_device.running = 0;119 120 ticks = rdc321x_wdt_device.default_ticks;121 122 return -EIO;123}124 125/* filesystem operations */126static int rdc321x_wdt_open(struct inode *inode, struct file *file)127{128 if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))129 return -EBUSY;130 131 return stream_open(inode, file);132}133 134static int rdc321x_wdt_release(struct inode *inode, struct file *file)135{136 clear_bit(0, &rdc321x_wdt_device.inuse);137 return 0;138}139 140static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,141 unsigned long arg)142{143 void __user *argp = (void __user *)arg;144 u32 value;145 static const struct watchdog_info ident = {146 .options = WDIOF_CARDRESET,147 .identity = "RDC321x WDT",148 };149 unsigned long flags;150 151 switch (cmd) {152 case WDIOC_KEEPALIVE:153 rdc321x_wdt_reset();154 break;155 case WDIOC_GETSTATUS:156 /* Read the value from the DATA register */157 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);158 pci_read_config_dword(rdc321x_wdt_device.sb_pdev,159 rdc321x_wdt_device.base_reg, &value);160 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);161 if (copy_to_user(argp, &value, sizeof(u32)))162 return -EFAULT;163 break;164 case WDIOC_GETSUPPORT:165 if (copy_to_user(argp, &ident, sizeof(ident)))166 return -EFAULT;167 break;168 case WDIOC_SETOPTIONS:169 if (copy_from_user(&value, argp, sizeof(int)))170 return -EFAULT;171 switch (value) {172 case WDIOS_ENABLECARD:173 rdc321x_wdt_start();174 break;175 case WDIOS_DISABLECARD:176 return rdc321x_wdt_stop();177 default:178 return -EINVAL;179 }180 break;181 default:182 return -ENOTTY;183 }184 return 0;185}186 187static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,188 size_t count, loff_t *ppos)189{190 if (!count)191 return -EIO;192 193 rdc321x_wdt_reset();194 195 return count;196}197 198static const struct file_operations rdc321x_wdt_fops = {199 .owner = THIS_MODULE,200 .unlocked_ioctl = rdc321x_wdt_ioctl,201 .compat_ioctl = compat_ptr_ioctl,202 .open = rdc321x_wdt_open,203 .write = rdc321x_wdt_write,204 .release = rdc321x_wdt_release,205};206 207static struct miscdevice rdc321x_wdt_misc = {208 .minor = WATCHDOG_MINOR,209 .name = "watchdog",210 .fops = &rdc321x_wdt_fops,211};212 213static int rdc321x_wdt_probe(struct platform_device *pdev)214{215 int err;216 struct resource *r;217 struct rdc321x_wdt_pdata *pdata;218 219 pdata = dev_get_platdata(&pdev->dev);220 if (!pdata) {221 dev_err(&pdev->dev, "no platform data supplied\n");222 return -ENODEV;223 }224 225 r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");226 if (!r) {227 dev_err(&pdev->dev, "failed to get wdt-reg resource\n");228 return -ENODEV;229 }230 231 rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;232 rdc321x_wdt_device.base_reg = r->start;233 rdc321x_wdt_device.queue = 0;234 rdc321x_wdt_device.default_ticks = ticks;235 236 err = misc_register(&rdc321x_wdt_misc);237 if (err < 0) {238 dev_err(&pdev->dev, "misc_register failed\n");239 return err;240 }241 242 spin_lock_init(&rdc321x_wdt_device.lock);243 244 /* Reset the watchdog */245 pci_write_config_dword(rdc321x_wdt_device.sb_pdev,246 rdc321x_wdt_device.base_reg, RDC_WDT_RST);247 248 init_completion(&rdc321x_wdt_device.stop);249 250 clear_bit(0, &rdc321x_wdt_device.inuse);251 252 timer_setup(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);253 254 dev_info(&pdev->dev, "watchdog init success\n");255 256 return 0;257}258 259static void rdc321x_wdt_remove(struct platform_device *pdev)260{261 if (rdc321x_wdt_device.queue) {262 rdc321x_wdt_device.queue = 0;263 wait_for_completion(&rdc321x_wdt_device.stop);264 }265 266 misc_deregister(&rdc321x_wdt_misc);267}268 269static struct platform_driver rdc321x_wdt_driver = {270 .probe = rdc321x_wdt_probe,271 .remove_new = rdc321x_wdt_remove,272 .driver = {273 .name = "rdc321x-wdt",274 },275};276 277module_platform_driver(rdc321x_wdt_driver);278 279MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");280MODULE_DESCRIPTION("RDC321x watchdog driver");281MODULE_LICENSE("GPL");282