From mboxrd@z Thu Jan 1 00:00:00 1970 From: viresh.kumar@st.com (Viresh KUMAR) Date: Mon, 17 May 2010 15:24:04 +0530 Subject: [PATCH] Watchdog: Adding support for Watchdog found with ARM CortexA9 Message-ID: <1274090044-10236-1-git-send-email-viresh.kumar@st.com> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org Watchdog details can be found in ARM Cortex A9 reference manual at: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0407e/indix.html Signed-off-by: Viresh Kumar --- drivers/watchdog/Kconfig | 7 + drivers/watchdog/Makefile | 1 + drivers/watchdog/cortexa9_wdt.c | 414 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+), 0 deletions(-) create mode 100755 drivers/watchdog/cortexa9_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0bf5020..b5c0f6f 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -73,6 +73,13 @@ config WM8350_WATCHDOG # ARM Architecture +config ARM_CORTEXA9_WATCHDOG + tristate "ARM CortexA9 Watchdog" + depends on ARM_AMBA + help + Watchdog timer present with ARM Cortex A9 processor. This will reboot + your system when the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on ARCH_AT91RM9200 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5e3cb95..7704efc 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ALPHA Architecture # ARM Architecture +obj-$(CONFIG_ARM_CORTEXA9_WATCHDOG) += cortexa9_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o diff --git a/drivers/watchdog/cortexa9_wdt.c b/drivers/watchdog/cortexa9_wdt.c new file mode 100755 index 0000000..f50ec64 --- /dev/null +++ b/drivers/watchdog/cortexa9_wdt.c @@ -0,0 +1,414 @@ +/* + * drivers/char/watchdog/cortexa9-wdt.c + * + * Watchdog driver for watchdog module present with ARM CORTEX A9 + * + * Copyright (C) 2010 ST Microelectronics + * Viresh Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* default timeout in seconds */ +#define DEFAULT_TIMEOUT 60 + +#define MODULE_NAME "cortexa9-wdt" + +/* watchdog register offsets and masks */ +#define WDTLOAD 0x000 + #define LOAD_MIN 0x00000000 + #define LOAD_MAX 0xFFFFFFFF +#define WDTCOUNTER 0x004 +#define WDTCONTROL 0x008 + /* control register masks */ + #define WDT_ENB (1 << 0) + #define WDT_ARLD_ENB (1 << 1) /* Autoreload enable */ + #define WDT_IRQ_ENB (1 << 2) + #define WDT_MODE (1 << 3) + #define WDT_PRESCALE(x) ((x) << 8) + #define PRESCALE_MIN 0x00 + #define PRESCALE_MAX 0xFF +#define WDTINTSTS 0x00C + #define INT_MASK 0x1 +#define WDTRSTSTS 0x010 + #define RST_MASK 0x1 +#define WDTDISABLE 0x014 + #define DISABLE_VAL1 0x12345678 + #define DISABLE_VAL2 0x87654321 + +/** + * struct cortexa9_wdt: cortex a9 wdt device structure + * + * lock: spin lock protecting dev structure and io access + * base: base address of wdt + * clk: clock structure of wdt + * pdev: platform device structure of wdt + * boot_status: boot time status of wdt + * status: current status of wdt + * prescale: prescale value to be set for current timeout + * load_val: load value to be set for current timeout + * timeout: current programmed timeout + */ +struct cortexa9_wdt { + spinlock_t lock; + void __iomem *base; + struct clk *clk; + struct platform_device *pdev; + unsigned long boot_status; + unsigned long status; + #define WDT_BUSY 0 + #define WDT_CAN_BE_CLOSED 1 + unsigned int prescale; + unsigned int load_val; + unsigned int timeout; +}; + +/* local variables */ +static struct cortexa9_wdt *wdt; +static int nowayout = WATCHDOG_NOWAYOUT; + +/* binary search */ +static inline void bsearch(u32 *var, u32 var_start, u32 var_end, + const u64 param, const u32 timeout, u32 rate) +{ + u64 tmp = 0; + + /* get the lowest var value that can satisfy our requirement */ + while (var_start < var_end) { + tmp = var_start; + tmp += var_end; + tmp = div_u64(tmp, 2); + if (timeout > div_u64((param + 1) * (tmp + 1), rate)) { + if (var_start == tmp) + break; + else + var_start = tmp; + } else { + if (var_end == tmp) + break; + else + var_end = tmp; + } + } + *var = tmp; +} + +/* + * This routine finds the most appropriate prescale and load value for input + * timout value + */ +static void wdt_config(unsigned int timeout) +{ + unsigned int psc, rate; + u64 load = 0; + + rate = clk_get_rate(wdt->clk); + + /* get appropriate value of psc and load */ + bsearch(&psc, PRESCALE_MIN, PRESCALE_MAX, LOAD_MAX, timeout, rate); + bsearch((u32 *)&load, LOAD_MIN, LOAD_MAX, psc, timeout, rate); + + spin_lock(&wdt->lock); + wdt->prescale = psc; + wdt->load_val = load; + + /* roundup timeout to closest positive integer value */ + wdt->timeout = div_u64((psc + 1) * (load + 1) + (rate / 2), rate); + spin_unlock(&wdt->lock); +} + +static void wdt_enable(void) +{ + u32 val; + + spin_lock(&wdt->lock); + + writel(wdt->load_val, wdt->base + WDTLOAD); + val = WDT_MODE | WDT_PRESCALE(wdt->prescale) | WDT_ENB; + writel(val, wdt->base + WDTCONTROL); + + spin_unlock(&wdt->lock); +} + +static void wdt_disable(void) +{ + spin_lock(&wdt->lock); + + /* firstly we need to put watchdog in timer mode */ + writel(DISABLE_VAL1, wdt->base + WDTDISABLE); + writel(DISABLE_VAL2, wdt->base + WDTDISABLE); + + writel(0, wdt->base + WDTCONTROL); + writel(INT_MASK, wdt->base + WDTINTSTS); + writel(RST_MASK, wdt->base + WDTRSTSTS); + + spin_unlock(&wdt->lock); +} + +static ssize_t cortexa9_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') { + set_bit(WDT_CAN_BE_CLOSED, + &wdt->status); + break; + } + } + } + wdt_enable(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, +}; + +static long cortexa9_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + int timeout; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(wdt->boot_status, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(timeout, (int *)arg); + if (ret) + break; + + wdt_config(timeout); + + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(wdt->timeout, (int *)arg); + break; + } + return ret; +} + +static int cortexa9_wdt_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + if (test_and_set_bit(WDT_BUSY, &wdt->status)) + return -EBUSY; + + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(&wdt->pdev->dev, "clock enable fail"); + goto err; + } + + wdt_enable(); + + /* can not be closed, once enabled */ + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + return nonseekable_open(inode, file); + +err: + clear_bit(WDT_BUSY, &wdt->status); + return ret; +} + +static int cortexa9_wdt_release(struct inode *inode, struct file *file) +{ + if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { + clear_bit(WDT_BUSY, &wdt->status); + dev_warn(&wdt->pdev->dev, "Device closed unexpectedly\n"); + return 0; + } + + wdt_disable(); + clk_disable(wdt->clk); + clear_bit(WDT_BUSY, &wdt->status); + + return 0; +} + +static const struct file_operations cortexa9_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = cortexa9_wdt_write, + .unlocked_ioctl = cortexa9_wdt_ioctl, + .open = cortexa9_wdt_open, + .release = cortexa9_wdt_release, +}; + +static struct miscdevice cortexa9_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &cortexa9_wdt_fops, +}; + +static int __devinit cortexa9_wdt_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOENT; + dev_warn(&pdev->dev, "Memory resource not defined\n"); + goto err; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_warn(&pdev->dev, "Failed to get memory region resource\n"); + ret = -ENOENT; + goto err; + } + + wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); + if (!wdt) { + dev_warn(&pdev->dev, "Kzalloc failed\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + + wdt->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_warn(&pdev->dev, "Clock not found\n"); + ret = PTR_ERR(wdt->clk); + goto err_clk_get; + } + + wdt->base = ioremap(res->start, resource_size(res)); + if (!wdt->base) { + ret = -ENOMEM; + dev_warn(&pdev->dev, "ioremap fail\n"); + goto err_ioremap; + } + + spin_lock_init(&wdt->lock); + /* This checks if system booted after watchdog reset or not */ + ret = clk_enable(wdt->clk); + if (!ret) { + wdt->boot_status = (readl(wdt->base + WDTRSTSTS) & RST_MASK) ? + WDIOF_CARDRESET : 0; + wdt_disable(); + clk_disable(wdt->clk); + } + + wdt->pdev = pdev; + wdt_config(DEFAULT_TIMEOUT); + + ret = misc_register(&cortexa9_wdt_miscdev); + if (ret < 0) { + dev_warn(&pdev->dev, "cannot register misc device\n"); + goto err_misc_register; + } + + return 0; + +err_misc_register: + iounmap(wdt->base); +err_ioremap: + clk_put(wdt->clk); +err_clk_get: + kfree(wdt); + wdt = NULL; +err_kzalloc: + release_mem_region(res->start, resource_size(res)); +err: + dev_err(&pdev->dev, "Probe Failed!!!\n"); + return ret; +} + +static int __devexit cortexa9_wdt_remove(struct platform_device *pdev) +{ + struct resource *res; + + misc_deregister(&cortexa9_wdt_miscdev); + iounmap(wdt->base); + clk_put(wdt->clk); + kfree(wdt); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + + return 0; +} + +static struct platform_driver cortexa9_wdt_driver = { + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + }, + .probe = cortexa9_wdt_probe, + .remove = __devexit_p(cortexa9_wdt_remove), +}; + +static int __init cortexa9_wdt_init(void) +{ + return platform_driver_register(&cortexa9_wdt_driver); +} +module_init(cortexa9_wdt_init); + +static void __exit cortexa9_wdt_exit(void) +{ + platform_driver_unregister(&cortexa9_wdt_driver); +} +module_exit(cortexa9_wdt_exit); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); + +MODULE_AUTHOR("Viresh Kumar "); +MODULE_DESCRIPTION("ARM CORTEX A9 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- 1.6.0.2