--- /dev/null
+/* linux/drivers/watchdog/rk29_wdt.c\r
+ *\r
+ * Copyright (C) 2011 ROCKCHIP, Inc.\r
+ * hhb@rock-chips.com\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program; if not, write to the Free Software\r
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+*/\r
+\r
+\r
+#include <linux/module.h>\r
+#include <linux/moduleparam.h>\r
+#include <linux/types.h>\r
+#include <linux/timer.h>\r
+#include <linux/miscdevice.h>\r
+#include <linux/watchdog.h>\r
+#include <linux/fs.h>\r
+#include <linux/init.h>\r
+#include <linux/platform_device.h>\r
+#include <linux/interrupt.h>\r
+#include <linux/clk.h>\r
+#include <linux/uaccess.h>\r
+#include <linux/io.h>\r
+\r
+#include <asm/mach/map.h>\r
+\r
+\r
+/* RK29 registers define */\r
+#define RK29_WDT_CR 0X00\r
+#define RK29_WDT_TORR 0X04\r
+#define RK29_WDT_CCVR 0X08\r
+#define RK29_WDT_CRR 0X0C\r
+#define RK29_WDT_STAT 0X10\r
+#define RK29_WDT_EOI 0X14\r
+\r
+#define RK29_WDT_EN 1\r
+#define RK29_RESPONSE_MODE 1\r
+#define RK29_RESET_PULSE 4\r
+\r
+//THAT wdt's clock is 24MHZ\r
+#define RK29_WDT_2730US 0\r
+#define RK29_WDT_5460US 1\r
+#define RK29_WDT_10920US 2\r
+#define RK29_WDT_21840US 3\r
+#define RK29_WDT_43680US 4\r
+#define RK29_WDT_87360US 5\r
+#define RK29_WDT_174720US 6\r
+#define RK29_WDT_349440US 7\r
+#define RK29_WDT_698880US 8\r
+#define RK29_WDT_1397760US 9\r
+#define RK29_WDT_2795520US 10\r
+#define RK29_WDT_5591040US 11\r
+#define RK29_WDT_11182080US 12\r
+#define RK29_WDT_22364160US 13\r
+#define RK29_WDT_44728320US 14\r
+#define RK29_WDT_89456640US 15\r
+\r
+/*\r
+#define CONFIG_RK29_WATCHDOG_ATBOOT (1)\r
+#define CONFIG_RK29_WATCHDOG_DEFAULT_TIME 10 //unit second\r
+#define CONFIG_RK29_WATCHDOG_DEBUG 1\r
+*/\r
+\r
+static int nowayout = WATCHDOG_NOWAYOUT;\r
+static int tmr_margin = CONFIG_RK29_WATCHDOG_DEFAULT_TIME;\r
+#ifdef CONFIG_RK29_WATCHDOG_ATBOOT\r
+static int tmr_atboot = 1;\r
+#else\r
+static int tmr_atboot = 0;\r
+#endif\r
+\r
+static int soft_noboot;\r
+\r
+#ifdef CONFIG_RK29_WATCHDOG_DEBUG\r
+static int debug = 1;\r
+#else\r
+static int debug = 0;\r
+#endif\r
+\r
+module_param(tmr_margin, int, 0);\r
+module_param(tmr_atboot, int, 0);\r
+module_param(nowayout, int, 0);\r
+module_param(soft_noboot, int, 0);\r
+module_param(debug, int, 0);\r
+\r
+MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default="\r
+ __MODULE_STRING(CONFIG_RK29_WATCHDOG_DEFAULT_TIME) ")");\r
+MODULE_PARM_DESC(tmr_atboot,\r
+ "Watchdog is started at boot time if set to 1, default="\r
+ __MODULE_STRING(CONFIG_RK29_WATCHDOG_ATBOOT));\r
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="\r
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");\r
+MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "\r
+ "0 to reboot (default depends on ONLY_TESTING)");\r
+MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");\r
+\r
+\r
+static unsigned long open_lock;\r
+static struct device *wdt_dev; /* platform device attached to */\r
+static struct resource *wdt_mem;\r
+static struct resource *wdt_irq;\r
+static struct clk *wdt_clock;\r
+static void __iomem *wdt_base;\r
+static unsigned int wdt_count;\r
+static char expect_close;\r
+\r
+static DEFINE_SPINLOCK(wdt_lock);\r
+\r
+/* watchdog control routines */\r
+\r
+#define DBG(msg...) do { \\r
+ if (debug) \\r
+ printk(KERN_INFO msg); \\r
+ } while (0)\r
+\r
+\r
+/* functions */\r
+static void rk29_wdt_keepalive(void)\r
+{\r
+ spin_lock(&wdt_lock);\r
+ writel(0x76, wdt_base + RK29_WDT_CRR);\r
+ spin_unlock(&wdt_lock);\r
+}\r
+\r
+static void __rk29_wdt_stop(void)\r
+{\r
+ unsigned long wtcon = 0;\r
+\r
+ wtcon = readl(wdt_base + RK29_WDT_CR);\r
+ wtcon &= 0xfffffffe;\r
+ writel(wtcon, wdt_base + RK29_WDT_CR);\r
+}\r
+\r
+static void rk29_wdt_stop(void)\r
+{\r
+ spin_lock(&wdt_lock);\r
+ __rk29_wdt_stop();\r
+ spin_unlock(&wdt_lock);\r
+}\r
+\r
+static void rk29_wdt_start(void)\r
+{\r
+ unsigned long wtcon = 0;\r
+\r
+ spin_lock(&wdt_lock);\r
+ __rk29_wdt_stop();\r
+ wtcon |= (RK29_WDT_EN << 0) | (RK29_RESPONSE_MODE << 1) | (RK29_RESET_PULSE << 2);\r
+ writel(wtcon, wdt_base + RK29_WDT_CR);\r
+ spin_unlock(&wdt_lock);\r
+}\r
+/* timeout unit us */\r
+static int rk29_wdt_set_heartbeat(int timeout)\r
+{\r
+ unsigned int freq = clk_get_rate(wdt_clock);\r
+ unsigned int count;\r
+ unsigned int torr = 0;\r
+ unsigned int acc = 1;\r
+\r
+ if (timeout < 1)\r
+ return -EINVAL;\r
+\r
+// freq /= 1000000;\r
+ count = timeout * freq;\r
+ count /= 0x10000;\r
+\r
+ while(acc < count){\r
+ acc *= 2;\r
+ torr++;\r
+ }\r
+ if(torr > 15){\r
+ torr = 15;\r
+ }\r
+ DBG("%s:%d\n", __func__, torr);\r
+ writel(torr, wdt_base + RK29_WDT_TORR);\r
+ return 0;\r
+}\r
+\r
+/*\r
+ * /dev/watchdog handling\r
+ */\r
+\r
+static int rk29_wdt_open(struct inode *inode, struct file *file)\r
+{\r
+ DBG("%s\n", __func__);\r
+ if (test_and_set_bit(0, &open_lock))\r
+ return -EBUSY;\r
+\r
+ if (nowayout)\r
+ __module_get(THIS_MODULE);\r
+\r
+ expect_close = 0;\r
+\r
+ /* start the timer */\r
+ rk29_wdt_start();\r
+ return nonseekable_open(inode, file);\r
+}\r
+\r
+static int rk29_wdt_release(struct inode *inode, struct file *file)\r
+{\r
+ /*\r
+ * Shut off the timer.\r
+ * Lock it in if it's a module and we set nowayout\r
+ */\r
+ DBG("%s\n", __func__);\r
+ if (expect_close == 42)\r
+ rk29_wdt_stop();\r
+ else {\r
+ dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");\r
+ rk29_wdt_keepalive();\r
+ }\r
+ expect_close = 0;\r
+ clear_bit(0, &open_lock);\r
+ return 0;\r
+}\r
+\r
+static ssize_t rk29_wdt_write(struct file *file, const char __user *data,\r
+ size_t len, loff_t *ppos)\r
+{\r
+ /*\r
+ * Refresh the timer.\r
+ */\r
+ DBG("%s\n", __func__);\r
+ if (len) {\r
+ if (!nowayout) {\r
+ size_t i;\r
+\r
+ /* In case it was set long ago */\r
+ expect_close = 0;\r
+\r
+ for (i = 0; i != len; i++) {\r
+ char c;\r
+\r
+ if (get_user(c, data + i))\r
+ return -EFAULT;\r
+ if (c == 'V')\r
+ expect_close = 42;\r
+ }\r
+ }\r
+ rk29_wdt_keepalive();\r
+ }\r
+ return len;\r
+}\r
+\r
+#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)\r
+\r
+static const struct watchdog_info rk29_wdt_ident = {\r
+ .options = OPTIONS,\r
+ .firmware_version = 0,\r
+ .identity = "rk29 Watchdog",\r
+};\r
+\r
+\r
+static long rk29_wdt_ioctl(struct file *file, unsigned int cmd,\r
+ unsigned long arg)\r
+{\r
+ void __user *argp = (void __user *)arg;\r
+ int __user *p = argp;\r
+ int new_margin;\r
+ DBG("%s\n", __func__);\r
+ switch (cmd) {\r
+ case WDIOC_GETSUPPORT:\r
+ return copy_to_user(argp, &rk29_wdt_ident,\r
+ sizeof(rk29_wdt_ident)) ? -EFAULT : 0;\r
+ case WDIOC_GETSTATUS:\r
+ case WDIOC_GETBOOTSTATUS:\r
+ return put_user(0, p);\r
+ case WDIOC_KEEPALIVE:\r
+ DBG("%s:rk29_wdt_keepalive\n", __func__);\r
+ rk29_wdt_keepalive();\r
+ return 0;\r
+ case WDIOC_SETTIMEOUT:\r
+ if (get_user(new_margin, p))\r
+ return -EFAULT;\r
+ if (rk29_wdt_set_heartbeat(new_margin))\r
+ return -EINVAL;\r
+ rk29_wdt_keepalive();\r
+ return put_user(tmr_margin, p);\r
+ case WDIOC_GETTIMEOUT:\r
+ return put_user(tmr_margin, p);\r
+ default:\r
+ return -ENOTTY;\r
+ }\r
+}\r
+\r
+\r
+\r
+/* kernel interface */\r
+\r
+static const struct file_operations rk29_wdt_fops = {\r
+ .owner = THIS_MODULE,\r
+ .llseek = no_llseek,\r
+ .write = rk29_wdt_write,\r
+ .unlocked_ioctl = rk29_wdt_ioctl,\r
+ .open = rk29_wdt_open,\r
+ .release = rk29_wdt_release,\r
+};\r
+\r
+static struct miscdevice rk29_wdt_miscdev = {\r
+ .minor = WATCHDOG_MINOR,\r
+ .name = "watchdog",\r
+ .fops = &rk29_wdt_fops,\r
+};\r
+\r
+\r
+/* interrupt handler code */\r
+\r
+static irqreturn_t rk29_wdt_irq_handler(int irqno, void *param)\r
+{\r
+ DBG("RK29_wdt:watchdog timer expired (irq)\n");\r
+ rk29_wdt_keepalive();\r
+ return IRQ_HANDLED;\r
+}\r
+\r
+\r
+/* device interface */\r
+\r
+static int __devinit rk29_wdt_probe(struct platform_device *pdev)\r
+{\r
+ struct resource *res;\r
+ struct device *dev;\r
+ int started = 0;\r
+ int ret;\r
+ int size;\r
+\r
+ dev = &pdev->dev;\r
+ wdt_dev = &pdev->dev;\r
+\r
+ /* get the memory region for the watchdog timer */\r
+\r
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);\r
+ if (res == NULL) {\r
+ dev_err(dev, "no memory resource specified\n");\r
+ return -ENOENT;\r
+ }\r
+\r
+ size = (res->end - res->start) + 1;\r
+ wdt_mem = request_mem_region(res->start, size, pdev->name);\r
+ if (wdt_mem == NULL) {\r
+ dev_err(dev, "failed to get memory region\n");\r
+ ret = -ENOENT;\r
+ goto err_req;\r
+ }\r
+\r
+ wdt_base = ioremap(res->start, size);\r
+ if (wdt_base == NULL) {\r
+ dev_err(dev, "failed to ioremap() region\n");\r
+ ret = -EINVAL;\r
+ goto err_req;\r
+ }\r
+\r
+ DBG("probe: mapped wdt_base=%p\n", wdt_base);\r
+\r
+\r
+#ifdef CONFIG_RK29_FEED_DOG_BY_INTE\r
+\r
+ wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);\r
+ if (wdt_irq == NULL) {\r
+ dev_err(dev, "no irq resource specified\n");\r
+ ret = -ENOENT;\r
+ goto err_map;\r
+ }\r
+\r
+ ret = request_irq(wdt_irq->start, rk29_wdt_irq_handler, 0, pdev->name, pdev);\r
+\r
+ if (ret != 0) {\r
+ dev_err(dev, "failed to install irq (%d)\n", ret);\r
+ goto err_map;\r
+ }\r
+\r
+#endif\r
+\r
+ wdt_clock = clk_get(&pdev->dev, "wdt");\r
+ if (IS_ERR(wdt_clock)) {\r
+ dev_err(dev, "failed to find watchdog clock source\n");\r
+ ret = PTR_ERR(wdt_clock);\r
+ goto err_irq;\r
+ }\r
+\r
+ clk_enable(wdt_clock);\r
+\r
+ rk29_wdt_set_heartbeat(tmr_margin);\r
+\r
+ ret = misc_register(&rk29_wdt_miscdev);\r
+ if (ret) {\r
+ dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",\r
+ WATCHDOG_MINOR, ret);\r
+ goto err_clk;\r
+ }\r
+ printk("watchdog misc directory:%s\n", rk29_wdt_miscdev.nodename);\r
+ if (tmr_atboot && started == 0) {\r
+ dev_info(dev, "starting watchdog timer\n");\r
+ rk29_wdt_start();\r
+ } else if (!tmr_atboot) {\r
+ /* if we're not enabling the watchdog, then ensure it is\r
+ * disabled if it has been left running from the bootloader\r
+ * or other source */\r
+\r
+ rk29_wdt_stop();\r
+ }\r
+\r
+ return 0;\r
+\r
+ err_clk:\r
+ clk_disable(wdt_clock);\r
+ clk_put(wdt_clock);\r
+\r
+ err_irq:\r
+ free_irq(wdt_irq->start, pdev);\r
+\r
+ err_map:\r
+ iounmap(wdt_base);\r
+\r
+ err_req:\r
+ release_resource(wdt_mem);\r
+ kfree(wdt_mem);\r
+\r
+ return ret;\r
+}\r
+\r
+static int __devexit rk29_wdt_remove(struct platform_device *dev)\r
+{\r
+ release_resource(wdt_mem);\r
+ kfree(wdt_mem);\r
+ wdt_mem = NULL;\r
+\r
+ free_irq(wdt_irq->start, dev);\r
+ wdt_irq = NULL;\r
+\r
+ clk_disable(wdt_clock);\r
+ clk_put(wdt_clock);\r
+ wdt_clock = NULL;\r
+\r
+ iounmap(wdt_base);\r
+ misc_deregister(&rk29_wdt_miscdev);\r
+\r
+ return 0;\r
+}\r
+\r
+static void rk29_wdt_shutdown(struct platform_device *dev)\r
+{\r
+ rk29_wdt_stop();\r
+}\r
+\r
+#ifdef CONFIG_PM\r
+\r
+static int rk29_wdt_suspend(struct platform_device *dev, pm_message_t state)\r
+{\r
+ rk29_wdt_stop();\r
+ return 0;\r
+}\r
+\r
+static int rk29_wdt_resume(struct platform_device *dev)\r
+{\r
+ rk29_wdt_set_heartbeat(tmr_margin);\r
+ rk29_wdt_start();\r
+ return 0;\r
+}\r
+\r
+#else\r
+#define rk29_wdt_suspend NULL\r
+#define rk29_wdt_resume NULL\r
+#endif /* CONFIG_PM */\r
+\r
+\r
+static struct platform_driver rk29_wdt_driver = {\r
+ .probe = rk29_wdt_probe,\r
+ .remove = __devexit_p(rk29_wdt_remove),\r
+ .shutdown = rk29_wdt_shutdown,\r
+ .suspend = rk29_wdt_suspend,\r
+ .resume = rk29_wdt_resume,\r
+ .driver = {\r
+ .owner = THIS_MODULE,\r
+ .name = "rk29-wdt",\r
+ },\r
+};\r
+\r
+\r
+static char banner[] __initdata =\r
+ KERN_INFO "RK29 Watchdog Timer, (c) 2011 Rockchip Electronics\n";\r
+\r
+static int __init watchdog_init(void)\r
+{\r
+ printk(banner);\r
+ return platform_driver_register(&rk29_wdt_driver);\r
+}\r
+\r
+static void __exit watchdog_exit(void)\r
+{\r
+ platform_driver_unregister(&rk29_wdt_driver);\r
+}\r
+\r
+module_init(watchdog_init);\r
+module_exit(watchdog_exit);\r
+\r
+MODULE_AUTHOR("hhb@rock-chips.com");\r
+MODULE_DESCRIPTION("RK29 Watchdog Device Driver");\r
+MODULE_LICENSE("GPL");\r
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);\r
+MODULE_ALIAS("platform:rk29-wdt");\r