diff options
Diffstat (limited to 'drivers/watchdog/ast_wdt.c')
-rw-r--r-- | drivers/watchdog/ast_wdt.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/drivers/watchdog/ast_wdt.c b/drivers/watchdog/ast_wdt.c new file mode 100644 index 000000000000..845f1db3d66d --- /dev/null +++ b/drivers/watchdog/ast_wdt.c @@ -0,0 +1,519 @@ +/******************************************************************************** +* File Name : ast_wdt +* +* Copyright (C) 2012-2020 ASPEED Technology Inc. +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by the Free Software Foundation; +* either version 2 of the License, or (at your option) any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +********************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/semaphore.h> +#include <asm/uaccess.h> + +#include <linux/platform_device.h> +#include <asm/io.h> + +#ifdef CONFIG_COLDFIRE +#include <asm/arch/irqs.h> +#include <asm/arch/ast_wdt.h> +#include <asm/arch/platform.h> +#else +#include <mach/irqs.h> +#include <mach/ast_wdt.h> +#include <mach/platform.h> +#endif + +#define TICKS_PER_uSEC 1 + + +typedef unsigned char bool_T; + +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#define TRUE 1 +#define FALSE 0 + +#if defined(CONFIG_COLDFIRE) +#define WDT_BASE_VA AST_WDT_BASE + +#else +#define WDT_BASE_VA (IO_ADDRESS(AST_WDT_BASE)) +#endif + +#define WDT_CntSts (WDT_BASE_VA+0x00) +#define WDT_Reload (WDT_BASE_VA+0x04) +#define WDT_Restart (WDT_BASE_VA+0x08) +#define WDT_Ctrl (WDT_BASE_VA+0x0C) +#define WDT_TimeOut (WDT_BASE_VA+0x10) +#define WDT_Clr (WDT_BASE_VA+0x14) +#define WDT_RstWd (WDT_BASE_VA+0x18) + + +#define AST_READ_REG(r) (*((volatile unsigned int *) (r))) +#define AST_WRITE_REG(r,v) (*((volatile unsigned int *) (r)) = ((unsigned int) (v))) + + +#define WDT_CLK_SRC_EXT 0 +#define WDT_CLK_SRC_PCLK 1 + +//Global Variables +#define WD_TIMO 6 /* Default heartbeat = 6 seconds */ + +static int heartbeat = WD_TIMO; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static unsigned long wdt_is_open; +static char expect_close; + +//Function Declaration +int __init wdt_init(void); + +static irqreturn_t wdt_isr(int irq, void *devid, struct pt_regs *regs) +{ + /* clear timeout */ + AST_WRITE_REG(WDT_Clr, 1); + + return (IRQ_HANDLED); +} + +void wdt_disable(void) +{ + register unsigned int regVal; + + /* reset WDT_Ctrl[0] as 0 */ + regVal = AST_READ_REG(WDT_Ctrl); + regVal &= 0xFFFFFFFE; + AST_WRITE_REG(WDT_Ctrl, regVal); +} + +void wdt_sel_clk_src(unsigned char sourceClk) +{ + register unsigned int regVal; + + regVal = AST_READ_REG(WDT_Ctrl); + if (sourceClk == WDT_CLK_SRC_PCLK) + { + /* reset WDT_Ctrl[4] as 0 */ + regVal &= 0xFFFFFFEF; + } + else + { + /* set WDT_Ctrl[4] as 1 */ + regVal |= 0x00000010; + } + AST_WRITE_REG(WDT_Ctrl, regVal); +} + +void wdt_set_timeout_action(bool_T bResetOut, bool_T bIntrSys, bool_T bResetSys) +{ + register unsigned int regVal; + + regVal = AST_READ_REG(WDT_Ctrl); + + if (bResetOut) + { + /* set WDT_Ctrl[3] = 1 */ + regVal |= 0x00000008; + } + else + { + /* reset WDT_Ctrl[3] = 0 */ + regVal &= 0xFFFFFFF7; + } + + if (bIntrSys) + { + /* set WDT_Ctrl[2] = 1 */ + regVal |= 0x00000004; + } + else + { + /* reset WDT_Ctrl[2] = 0 */ + regVal &= 0xFFFFFFFB; + } + + if (bResetSys) + { + /* set WDT_Ctrl[1] = 1 */ + regVal |= 0x00000002; + } + else + { + /* reset WDT_Ctrl[1] = 0 */ + regVal &= 0xFFFFFFFD; + } + + AST_WRITE_REG(WDT_Ctrl, regVal); +} + +void wdt_enable(void) +{ + register unsigned int regVal; + + /* set WDT_Ctrl[0] as 1 */ + regVal = AST_READ_REG(WDT_Ctrl); + regVal |= 1; + AST_WRITE_REG(WDT_Ctrl, regVal); +} + +void wdt_restart_new(unsigned int nPeriod, int sourceClk, bool_T bResetOut, bool_T bIntrSys, bool_T bResetSys, bool_T bUpdated) +{ + wdt_disable(); + + AST_WRITE_REG(WDT_Reload, nPeriod); + + wdt_sel_clk_src(sourceClk); + + wdt_set_timeout_action(bResetOut, bIntrSys, bResetSys); + + AST_WRITE_REG(WDT_Restart, 0x4755); /* reload! */ + + if (!bUpdated) + wdt_enable(); +} + +void wdt_restart(void) +{ + wdt_disable(); + AST_WRITE_REG(WDT_Restart, 0x4755); /* reload! */ + wdt_enable(); +} + + +/** + * wdt_set_heartbeat: + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat value is + * incorrect we keep the old value and return -EINVAL. If successfull we + * return 0. + */ +static int wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 1000)) + return -EINVAL; + + heartbeat=t; + + wdt_restart_new(TICKS_PER_uSEC*1000000*t, WDT_CLK_SRC_EXT, FALSE, TRUE, FALSE, FALSE); + return 0; +} + +/* + Kernel Interfaces +*/ + +/** + * ast_wdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + + static ssize_t ast_wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) + { + if(count) + { + if (!nowayout) + { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) + { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdt_restart(); + } + return count; + } + +/** + * ast_wdt_ioctl: + * @inode: inode of the device + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ + +static int ast_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + + static struct watchdog_info ident = + { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "AST WDT", + }; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdt_restart(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + } +} +/** +* ast_wdt_open: +* @inode: inode of device +* @file: file handle to device +* +* The watchdog device has been opened. The watchdog device is single +* open and on opening we load the counters. Counter zero is a 100Hz +* cascade, into counter 1 which downcounts to reboot. When the counter +* triggers counter 2 downcounts the length of the reset pulse which +* set set to be as long as possible. +*/ + +static int ast_wdt_open(struct inode *inode, struct file *file) +{ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + // wdt_init(); + wdt_restart(); + return nonseekable_open(inode, file); +} + +/** +* ast_wdt_release: +* @inode: inode to board +* @file: file handle to board +* +* The watchdog has a configurable API. There is a religious dispute +* between people who want their watchdog to be able to shut down and +* those who want to be sure if the watchdog manager dies the machine +* reboots. In the former case we disable the counters, in the latter +* case you have to open it again very soon. +*/ + +static int ast_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42 || !nowayout) + { + wdt_disable(); + clear_bit(0, &wdt_is_open); + } + else + { + printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); + wdt_restart(); + } + expect_close = 0; + return 0; +} + +/** +* notify_sys: +* @this: our notifier block +* @code: the event being reported +* @unused: unused +* +* Our notifier is called on system shutdowns. We want to turn the card +* off at reboot otherwise the machine will reboot again during memory +* test or worse yet during the following fsck. This would suck, in fact +* trust me - if it happens it does suck. +*/ + +static int ast_wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) + { + /* Turn the WDT off */ + wdt_disable(); + } + return NOTIFY_DONE; +} + +extern void ast_soc_wdt_reset(void) +{ + writel(0x10 , WDT_BASE_VA+0x04); + writel(0x4755, WDT_BASE_VA+0x08); + writel(0x3, WDT_BASE_VA+0x0c); +} + +EXPORT_SYMBOL(ast_soc_wdt_reset); + +static struct file_operations ast_wdt_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ast_wdt_write, + .ioctl = ast_wdt_ioctl, + .open = ast_wdt_open, + .release = ast_wdt_release, +}; + +static struct miscdevice ast_wdt_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ast_wdt_fops, +}; + +static struct notifier_block ast_wdt_notifier = +{ + .notifier_call=ast_wdt_notify_sys, +}; + +static int ast_wdt_probe(struct platform_device *pdev) +{ + int ret; + + wdt_disable(); + wdt_sel_clk_src(WDT_CLK_SRC_EXT); + wdt_set_timeout_action(FALSE, FALSE, FALSE); + + /* register ISR */ + if (request_irq(IRQ_WDT, (void *)wdt_isr, IRQF_DISABLED, "WDT", NULL)) + { + printk("unable to register interrupt INT_WDT = %d\n", IRQ_WDT); + return (-1); + } + else + printk("success to register interrupt for INT_WDT (%d)\n", IRQ_WDT); + + ret = register_reboot_notifier(&ast_wdt_notifier); + if(ret) + { + printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret); + free_irq(IRQ_WDT, NULL); + return ret; + } + + ret = misc_register(&ast_wdt_miscdev); + if (ret) + { + printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&ast_wdt_notifier); + return ret; + } + + /* interrupt the system while WDT timeout */ + wdt_restart_new(TICKS_PER_uSEC*1000000*heartbeat, WDT_CLK_SRC_EXT, FALSE, TRUE, FALSE, TRUE); + + printk(KERN_INFO "AST WDT is installed.(irq = %d, heartbeat = %d secs, nowayout = %d)\n",IRQ_WDT,heartbeat,nowayout); + + return (0); +} + +static int ast_wdt_remove(struct platform_device *dev) +{ + misc_deregister(&ast_wdt_miscdev); + disable_irq(IRQ_WDT); + free_irq(IRQ_WDT, NULL); + return 0; +} + +static void ast_wdt_shutdown(struct platform_device *dev) +{ + wdt_disable(); +} + +static struct platform_driver ast_wdt_driver = { + .probe = ast_wdt_probe, + .remove = ast_wdt_remove, + .shutdown = ast_wdt_shutdown, +#if 0 + .suspend = ast_wdt_suspend, + .resume = ast_wdt_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "ast-wdt", + }, +}; + +static char banner[] __initdata = KERN_INFO "ASPEED Watchdog Timer, ASPEED Technology Inc.\n"; + +static int __init watchdog_init(void) +{ + printk(banner); + + return platform_driver_register(&ast_wdt_driver); +} + +static void __exit watchdog_exit(void) +{ + platform_driver_unregister(&ast_wdt_driver); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_DESCRIPTION("Driver for AST Watch Dog"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_LICENSE("GPL"); |