From mboxrd@z Thu Jan 1 00:00:00 1970 From: viresh.kumar@st.com (Viresh KUMAR) Date: Mon, 17 May 2010 15:26:03 +0530 Subject: [PATCH] Watchdog: Adding support for Watchdog found with ARM CortexA9 In-Reply-To: <1274090044-10236-1-git-send-email-viresh.kumar@st.com> References: <1274090044-10236-1-git-send-email-viresh.kumar@st.com> Message-ID: <4BF112B3.5000001@st.com> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org On 5/17/2010 3:24 PM, Viresh KUMAR wrote: > 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 Please ignore this. mode is incorrect, i will resend it. > > 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);