Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * drivers/watchdog/ar7_wdt.c
0004  *
0005  * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
0006  * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
0007  *
0008  * Some code taken from:
0009  * National Semiconductor SCx200 Watchdog support
0010  * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
0011  *
0012  */
0013 
0014 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0015 
0016 #include <linux/module.h>
0017 #include <linux/moduleparam.h>
0018 #include <linux/errno.h>
0019 #include <linux/miscdevice.h>
0020 #include <linux/platform_device.h>
0021 #include <linux/watchdog.h>
0022 #include <linux/fs.h>
0023 #include <linux/ioport.h>
0024 #include <linux/io.h>
0025 #include <linux/uaccess.h>
0026 #include <linux/clk.h>
0027 
0028 #include <asm/addrspace.h>
0029 #include <asm/mach-ar7/ar7.h>
0030 
0031 #define LONGNAME "TI AR7 Watchdog Timer"
0032 
0033 MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
0034 MODULE_DESCRIPTION(LONGNAME);
0035 MODULE_LICENSE("GPL");
0036 
0037 static int margin = 60;
0038 module_param(margin, int, 0);
0039 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
0040 
0041 static bool nowayout = WATCHDOG_NOWAYOUT;
0042 module_param(nowayout, bool, 0);
0043 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
0044 
0045 #define READ_REG(x) readl((void __iomem *)&(x))
0046 #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
0047 
0048 struct ar7_wdt {
0049     u32 kick_lock;
0050     u32 kick;
0051     u32 change_lock;
0052     u32 change;
0053     u32 disable_lock;
0054     u32 disable;
0055     u32 prescale_lock;
0056     u32 prescale;
0057 };
0058 
0059 static unsigned long wdt_is_open;
0060 static unsigned expect_close;
0061 static DEFINE_SPINLOCK(wdt_lock);
0062 
0063 /* XXX currently fixed, allows max margin ~68.72 secs */
0064 #define prescale_value 0xffff
0065 
0066 /* Pointer to the remapped WDT IO space */
0067 static struct ar7_wdt *ar7_wdt;
0068 
0069 static struct clk *vbus_clk;
0070 
0071 static void ar7_wdt_kick(u32 value)
0072 {
0073     WRITE_REG(ar7_wdt->kick_lock, 0x5555);
0074     if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
0075         WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
0076         if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
0077             WRITE_REG(ar7_wdt->kick, value);
0078             return;
0079         }
0080     }
0081     pr_err("failed to unlock WDT kick reg\n");
0082 }
0083 
0084 static void ar7_wdt_prescale(u32 value)
0085 {
0086     WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
0087     if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
0088         WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
0089         if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
0090             WRITE_REG(ar7_wdt->prescale, value);
0091             return;
0092         }
0093     }
0094     pr_err("failed to unlock WDT prescale reg\n");
0095 }
0096 
0097 static void ar7_wdt_change(u32 value)
0098 {
0099     WRITE_REG(ar7_wdt->change_lock, 0x6666);
0100     if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
0101         WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
0102         if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
0103             WRITE_REG(ar7_wdt->change, value);
0104             return;
0105         }
0106     }
0107     pr_err("failed to unlock WDT change reg\n");
0108 }
0109 
0110 static void ar7_wdt_disable(u32 value)
0111 {
0112     WRITE_REG(ar7_wdt->disable_lock, 0x7777);
0113     if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
0114         WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
0115         if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
0116             WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
0117             if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
0118                 WRITE_REG(ar7_wdt->disable, value);
0119                 return;
0120             }
0121         }
0122     }
0123     pr_err("failed to unlock WDT disable reg\n");
0124 }
0125 
0126 static void ar7_wdt_update_margin(int new_margin)
0127 {
0128     u32 change;
0129     u32 vbus_rate;
0130 
0131     vbus_rate = clk_get_rate(vbus_clk);
0132     change = new_margin * (vbus_rate / prescale_value);
0133     if (change < 1)
0134         change = 1;
0135     if (change > 0xffff)
0136         change = 0xffff;
0137     ar7_wdt_change(change);
0138     margin = change * prescale_value / vbus_rate;
0139     pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
0140         margin, prescale_value, change, vbus_rate);
0141 }
0142 
0143 static void ar7_wdt_enable_wdt(void)
0144 {
0145     pr_debug("enabling watchdog timer\n");
0146     ar7_wdt_disable(1);
0147     ar7_wdt_kick(1);
0148 }
0149 
0150 static void ar7_wdt_disable_wdt(void)
0151 {
0152     pr_debug("disabling watchdog timer\n");
0153     ar7_wdt_disable(0);
0154 }
0155 
0156 static int ar7_wdt_open(struct inode *inode, struct file *file)
0157 {
0158     /* only allow one at a time */
0159     if (test_and_set_bit(0, &wdt_is_open))
0160         return -EBUSY;
0161     ar7_wdt_enable_wdt();
0162     expect_close = 0;
0163 
0164     return stream_open(inode, file);
0165 }
0166 
0167 static int ar7_wdt_release(struct inode *inode, struct file *file)
0168 {
0169     if (!expect_close)
0170         pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
0171     else if (!nowayout)
0172         ar7_wdt_disable_wdt();
0173     clear_bit(0, &wdt_is_open);
0174     return 0;
0175 }
0176 
0177 static ssize_t ar7_wdt_write(struct file *file, const char *data,
0178                  size_t len, loff_t *ppos)
0179 {
0180     /* check for a magic close character */
0181     if (len) {
0182         size_t i;
0183 
0184         spin_lock(&wdt_lock);
0185         ar7_wdt_kick(1);
0186         spin_unlock(&wdt_lock);
0187 
0188         expect_close = 0;
0189         for (i = 0; i < len; ++i) {
0190             char c;
0191             if (get_user(c, data + i))
0192                 return -EFAULT;
0193             if (c == 'V')
0194                 expect_close = 1;
0195         }
0196 
0197     }
0198     return len;
0199 }
0200 
0201 static long ar7_wdt_ioctl(struct file *file,
0202                     unsigned int cmd, unsigned long arg)
0203 {
0204     static const struct watchdog_info ident = {
0205         .identity = LONGNAME,
0206         .firmware_version = 1,
0207         .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
0208                         WDIOF_MAGICCLOSE),
0209     };
0210     int new_margin;
0211 
0212     switch (cmd) {
0213     case WDIOC_GETSUPPORT:
0214         if (copy_to_user((struct watchdog_info *)arg, &ident,
0215                 sizeof(ident)))
0216             return -EFAULT;
0217         return 0;
0218     case WDIOC_GETSTATUS:
0219     case WDIOC_GETBOOTSTATUS:
0220         if (put_user(0, (int *)arg))
0221             return -EFAULT;
0222         return 0;
0223     case WDIOC_KEEPALIVE:
0224         ar7_wdt_kick(1);
0225         return 0;
0226     case WDIOC_SETTIMEOUT:
0227         if (get_user(new_margin, (int *)arg))
0228             return -EFAULT;
0229         if (new_margin < 1)
0230             return -EINVAL;
0231 
0232         spin_lock(&wdt_lock);
0233         ar7_wdt_update_margin(new_margin);
0234         ar7_wdt_kick(1);
0235         spin_unlock(&wdt_lock);
0236         fallthrough;
0237     case WDIOC_GETTIMEOUT:
0238         if (put_user(margin, (int *)arg))
0239             return -EFAULT;
0240         return 0;
0241     default:
0242         return -ENOTTY;
0243     }
0244 }
0245 
0246 static const struct file_operations ar7_wdt_fops = {
0247     .owner      = THIS_MODULE,
0248     .write      = ar7_wdt_write,
0249     .unlocked_ioctl = ar7_wdt_ioctl,
0250     .compat_ioctl   = compat_ptr_ioctl,
0251     .open       = ar7_wdt_open,
0252     .release    = ar7_wdt_release,
0253     .llseek     = no_llseek,
0254 };
0255 
0256 static struct miscdevice ar7_wdt_miscdev = {
0257     .minor      = WATCHDOG_MINOR,
0258     .name       = "watchdog",
0259     .fops       = &ar7_wdt_fops,
0260 };
0261 
0262 static int ar7_wdt_probe(struct platform_device *pdev)
0263 {
0264     int rc;
0265 
0266     ar7_wdt = devm_platform_ioremap_resource_byname(pdev, "regs");
0267     if (IS_ERR(ar7_wdt))
0268         return PTR_ERR(ar7_wdt);
0269 
0270     vbus_clk = clk_get(NULL, "vbus");
0271     if (IS_ERR(vbus_clk)) {
0272         pr_err("could not get vbus clock\n");
0273         return PTR_ERR(vbus_clk);
0274     }
0275 
0276     ar7_wdt_disable_wdt();
0277     ar7_wdt_prescale(prescale_value);
0278     ar7_wdt_update_margin(margin);
0279 
0280     rc = misc_register(&ar7_wdt_miscdev);
0281     if (rc) {
0282         pr_err("unable to register misc device\n");
0283         goto out;
0284     }
0285     return 0;
0286 
0287 out:
0288     clk_put(vbus_clk);
0289     vbus_clk = NULL;
0290     return rc;
0291 }
0292 
0293 static int ar7_wdt_remove(struct platform_device *pdev)
0294 {
0295     misc_deregister(&ar7_wdt_miscdev);
0296     clk_put(vbus_clk);
0297     vbus_clk = NULL;
0298     return 0;
0299 }
0300 
0301 static void ar7_wdt_shutdown(struct platform_device *pdev)
0302 {
0303     if (!nowayout)
0304         ar7_wdt_disable_wdt();
0305 }
0306 
0307 static struct platform_driver ar7_wdt_driver = {
0308     .probe = ar7_wdt_probe,
0309     .remove = ar7_wdt_remove,
0310     .shutdown = ar7_wdt_shutdown,
0311     .driver = {
0312         .name = "ar7_wdt",
0313     },
0314 };
0315 
0316 module_platform_driver(ar7_wdt_driver);