diff options
author | Darius Augulis <augulis.darius@gmail.com> | 2009-03-09 17:10:25 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2009-06-23 13:16:08 +0200 |
commit | be4eeae3699957ea9a1aaca8e08e5f706bb160ea (patch) | |
tree | 711ddba9e96342960ee21c4a94ec9eabd4374097 | |
parent | 459d05e55039b33927afbc5b1ef8a346e4b4bedf (diff) | |
download | linux-2.6-be4eeae3699957ea9a1aaca8e08e5f706bb160ea.tar.gz linux-2.6-be4eeae3699957ea9a1aaca8e08e5f706bb160ea.tar.xz |
Watchdog driver for IMX/MXC
Driver for watchdog timer on Freesale IMX processors.
Signed-off-by: Darius Augulis <augulis.darius@gmail.com>
-rw-r--r-- | drivers/watchdog/Kconfig | 12 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/imx-wdt.c | 432 |
3 files changed, 445 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b166f2852a6..57b4021c42c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -66,6 +66,18 @@ config WM8350_WATCHDOG # ARM Architecture +config IMX_WDT + tristate "IMX Watchdog" + depends on ARCH_MXC + help + This is the driver for the hardware watchdog + on the Freescale IMX processors. + If you have one of these processors and wish to have + watchdog support enabled, say Y, otherwise say N. + + To compile this driver as a module, choose M here: the + module will be called imx_wdt. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on ARCH_AT91RM9200 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c3afa14d5be..bb829a01da5 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o +obj-$(CONFIG_IMX_WDT) += imx-wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/imx-wdt.c b/drivers/watchdog/imx-wdt.c new file mode 100644 index 00000000000..804777700a6 --- /dev/null +++ b/drivers/watchdog/imx-wdt.c @@ -0,0 +1,432 @@ +/* + * Watchdog driver for IMX processors + * + * Copyright (C) 2008 Darius Augulis <augulis.darius@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +/* + * NOTE: MX1 arch has a bit different Watchdog than MX2 and MX3. + * It's not possible to turn off watchdog on MX2 or MX3 since it's enabled. + * Timeout changing with IOCTL command is possible only on MX1. + * WD timer halting during suspend is implemented in all archs but in + * different way. + * MX2 and MX3 has 16 bit watchdog registers compared to 32 bit on MX1. + * + */ + +/*#define DEBUG*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> +#include <linux/clk.h> + +#ifdef CONFIG_ARCH_MX1 +# define WDT_MAX_TIME 63 /* seconds */ + +# define IMX_WDT_WCR 0x00 /* Control reg */ +# define IMX_WDT_WSR 0x04 /* Service reg */ +# define IMX_WDT_WSTR 0x08 /* Status reg */ + +# define WCR_WT (0x7F<<8) +# define WCR_WDEC (1<<1) +# define WCR_WDE (1<<0) +# define WCR_WHALT (1<<15) +#else +# define WDT_MAX_TIME 127 /* seconds */ + +# define IMX_WDT_WCR 0x00 /* Control reg */ +# define IMX_WDT_WSR 0x02 /* Service reg */ +# define IMX_WDT_WSTR 0x04 /* Status reg */ + +# define WCR_WT (0xFF<<8) +# define WCR_WDE (1<<2) +# define WCR_WDZST (1<<0) +#endif + +#define WDT_DEFAULT_TIME 10 /* seconds */ +#define WDT_SEQ1 0x5555 +#define WDT_SEQ2 0xAAAA + +static struct platform_device *imx_wdt_dev; + +struct imx_wdt_struct { + struct resource *res; + struct device *dev; + struct clk *clk; + void __iomem *base; + int wdt_time; + int nowayout; + unsigned long status; +}; + +static unsigned int timeout = WDT_DEFAULT_TIME; + +static struct watchdog_info imx_wdt_info = { + .identity = "imx watchdog", + .options = WDIOF_KEEPALIVEPING +#ifdef CONFIG_ARCH_MX1 + | WDIOF_SETTIMEOUT +#endif +}; + +/* Disable the watchdog. */ +static inline void imx_wdt_stop(struct imx_wdt_struct *imx_wdt) +{ + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + +#ifdef CONFIG_ARCH_MX1 + __raw_writew(0, imx_wdt->base + IMX_WDT_WCR); +#else + dev_info(imx_wdt->dev, "watchdog is unstoppable on i.MX2/3 !\n"); +#endif +} + +/* Enable and reset the watchdog. */ +static inline void imx_wdt_start(struct imx_wdt_struct *imx_wdt) +{ + u16 temp; + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + temp = __raw_readw(imx_wdt->base + IMX_WDT_WCR); + temp |= imx_wdt->wdt_time << 8; + __raw_writew(temp, imx_wdt->base + IMX_WDT_WCR); + +#ifdef CONFIG_ARCH_MX1 +# ifndef CONFIG_WATCHDOG_NOWAYOUT + temp |= WCR_WDEC; + __raw_writew(temp, imx_wdt->base + IMX_WDT_WCR); +# endif + temp |= WCR_WDE; + __raw_writew(temp, imx_wdt->base + IMX_WDT_WCR); +#else + temp |= WCR_WDE | WCR_WDZST; + __raw_writew(temp, imx_wdt->base + IMX_WDT_WCR); +#endif + +#ifdef CONFIG_ARCH_MX1 + __raw_writew(WDT_SEQ1, imx_wdt->base + IMX_WDT_WSR); + __raw_writew(WDT_SEQ2, imx_wdt->base + IMX_WDT_WSR); +#endif +} + +/* Service the watchdog */ +static inline void imx_wdt_service(struct imx_wdt_struct *imx_wdt) +{ + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + __raw_writew(WDT_SEQ1, imx_wdt->base + IMX_WDT_WSR); + __raw_writew(WDT_SEQ2, imx_wdt->base + IMX_WDT_WSR); +} + +/* Watchdog device is opened, and watchdog starts running. */ +static int imx_wdt_open(struct inode *inode, struct file *file) +{ + struct imx_wdt_struct *imx_wdt = platform_get_drvdata(imx_wdt_dev); + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + if (test_and_set_bit(0, &imx_wdt->status)) + return -EBUSY; + + file->private_data = imx_wdt; + + imx_wdt_start(imx_wdt); + return nonseekable_open(inode, file); +} + +/* Close the watchdog device. */ +static int imx_wdt_close(struct inode *inode, struct file *file) +{ + struct imx_wdt_struct *imx_wdt = file->private_data; + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + /* Disable the watchdog if possible */ + if (!imx_wdt->nowayout) + imx_wdt_stop(imx_wdt); + + clear_bit(0, &imx_wdt->status); + return 0; +} + +#ifdef CONFIG_ARCH_MX1 +/* Change the watchdog time interval. */ +static int imx_wdt_settimeout(struct imx_wdt_struct *imx_wdt, int new_time) +{ + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + if ((new_time * 2 < 1) || (new_time * 2 > WDT_MAX_TIME)) + return -EINVAL; + + imx_wdt->wdt_time = new_time * 2; + return 0; +} +#endif + +/* Handle commands from user-space. */ +static int imx_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct imx_wdt_struct *imx_wdt = file->private_data; + + void __user *argp = (void __user *)arg; + int __user *p = argp; +#ifdef CONFIG_ARCH_MX1 + int new_value; +#endif + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + switch (cmd) { + case WDIOC_KEEPALIVE: + imx_wdt_service(imx_wdt); + return 0; + + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &imx_wdt_info, + sizeof(imx_wdt_info)) ? -EFAULT : 0; +#ifdef CONFIG_ARCH_MX1 + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + if (imx_wdt_settimeout(imx_wdt, new_value)) + return -EINVAL; + + /* Enable new time value */ + imx_wdt_start(imx_wdt); + + /* Return current value */ + return put_user(imx_wdt->wdt_time / 2, p); +#endif + case WDIOC_GETTIMEOUT: + return put_user(imx_wdt->wdt_time / 2, p); + + default: + return -ENOTTY; + } +} + +/* Refresh the watchdog whenever device is written to. */ +static ssize_t imx_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + struct imx_wdt_struct *imx_wdt = file->private_data; + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + imx_wdt_service(imx_wdt); + return len; +} + +static const struct file_operations imx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = imx_wdt_ioctl, + .open = imx_wdt_open, + .release = imx_wdt_close, + .write = imx_wdt_write, +}; + +static struct miscdevice imx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &imx_wdt_fops, +}; + +static void imx_wdt_shutdown(struct platform_device *pdev) +{ + struct imx_wdt_struct *imx_wdt = platform_get_drvdata(pdev); + + dev_dbg(imx_wdt->dev, "<%s>\n", __func__); + + imx_wdt_stop(imx_wdt); +} + +static int __init imx_wdt_probe(struct platform_device *pdev) +{ + int ret; + int res_size; + struct resource *res; + void __iomem *base; + struct imx_wdt_struct *imx_wdt; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "can't get device resources\n"); + return -ENODEV; + } + + res_size = res->end - res->start + 1; + if (!request_mem_region(res->start, res_size, res->name)) { + dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n", + res_size, res->start); + return -ENOMEM; + } + + base = ioremap(res->start, res_size); + if (!base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -EIO; + goto fail0; + } + + imx_wdt = kzalloc(sizeof(struct imx_wdt_struct), GFP_KERNEL); + if (!imx_wdt) { + dev_err(&pdev->dev, "can't allocate interface\n"); + ret = -ENOMEM; + goto fail1; + } + + imx_wdt->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(imx_wdt->clk)) { + dev_err(&pdev->dev, "can't get Watchdog clock\n"); + ret = PTR_ERR(imx_wdt->clk); + goto fail2; + } + clk_enable(imx_wdt->clk); + + /* Setup imx_wdt driver structure */ + imx_wdt->dev = &pdev->dev; + imx_wdt->base = base; + imx_wdt->res = res; + imx_wdt->wdt_time = timeout * 2; +#ifdef CONFIG_WATCHDOG_NOWAYOUT + imx_wdt->nowayout = 1; +#else + imx_wdt->nowayout = 0; +#endif + + /* Set up platform driver data */ + platform_set_drvdata(pdev, imx_wdt); + imx_wdt_dev = pdev; + + if (imx_wdt_miscdev.parent) { + ret = -EBUSY; + goto fail3; + } + imx_wdt_miscdev.parent = &pdev->dev; + + ret = misc_register(&imx_wdt_miscdev); + if (ret) + goto fail3; + + dev_dbg(&pdev->dev, "IMX Watchdog Timer enabled\n"); + return 0; + +fail3: + clk_disable(imx_wdt->clk); + clk_put(imx_wdt->clk); +fail2: + platform_set_drvdata(pdev, NULL); + kfree(imx_wdt); +fail1: + iounmap(base); +fail0: + release_mem_region(res->start, res_size); + + return ret; /* Return error number */ +} + +static int __exit imx_wdt_remove(struct platform_device *pdev) +{ + struct imx_wdt_struct *imx_wdt = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "IMX Watchdog Timer disabled\n"); + + platform_set_drvdata(pdev, NULL); + misc_deregister(&imx_wdt_miscdev); + imx_wdt_dev = NULL; + iounmap(imx_wdt->base); + release_mem_region(imx_wdt->res->start, + imx_wdt->res->end - imx_wdt->res->start + 1); + + clk_disable(imx_wdt->clk); + clk_put(imx_wdt->clk); + + kfree(imx_wdt); + return 0; +} + +#ifdef CONFIG_PM + +static int imx_wdt_suspend(struct platform_device *pdev, pm_message_t message) +{ +#ifdef CONFIG_ARCH_MX1 + struct imx_wdt_struct *imx_wdt = platform_get_drvdata(pdev); + + u32 temp = __raw_readw(imx_wdt->base + IMX_WDT_WCR); + __raw_writew(temp | WCR_WHALT, imx_wdt->base + IMX_WDT_WCR); +#endif + return 0; +} + +static int imx_wdt_resume(struct platform_device *pdev) +{ +#ifdef CONFIG_ARCH_MX1 + struct imx_wdt_struct *imx_wdt = platform_get_drvdata(pdev); + u32 temp; + + if (imx_wdt->status) { + temp = __raw_readw(imx_wdt->base + IMX_WDT_WCR) & ~WCR_WHALT; + __raw_writew(temp, imx_wdt->base + IMX_WDT_WCR); + } +#endif + return 0; +} + +#else +#define imx_wdt_suspend NULL +#define imx_wdt_resume NULL +#endif + +static struct platform_driver imx_wdt_driver = { + .probe = imx_wdt_probe, + .remove = __exit_p(imx_wdt_remove), + .shutdown = imx_wdt_shutdown, + .suspend = imx_wdt_suspend, + .resume = imx_wdt_resume, + .driver = { + .name = "imx-wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init imx_wdt_init(void) +{ + return platform_driver_probe(&imx_wdt_driver, imx_wdt_probe); +} + +static void __exit imx_wdt_exit(void) +{ + platform_driver_unregister(&imx_wdt_driver); +} +module_init(imx_wdt_init); +module_exit(imx_wdt_exit); + +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); + +MODULE_AUTHOR("Darius Augulis"); +MODULE_DESCRIPTION("Watchdog driver for IMX"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:imx-wdt"); |