From 254d8a82777290671df32e2574b337bffc84985a Mon Sep 17 00:00:00 2001 From: hhb Date: Tue, 5 Jul 2011 20:46:18 +0800 Subject: [PATCH] rk29: add watch dog driver --- arch/arm/mach-rk29/board-rk29-ddr3sdk.c | 5 + arch/arm/mach-rk29/board-rk29-phonesdk.c | 5 + arch/arm/mach-rk29/board-rk29phonepadsdk.c | 5 + arch/arm/mach-rk29/board-rk29sdk.c | 5 + arch/arm/mach-rk29/devices.c | 30 ++ arch/arm/mach-rk29/devices.h | 2 + drivers/watchdog/Kconfig | 26 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/rk29_wdt.c | 510 +++++++++++++++++++++ 9 files changed, 589 insertions(+) mode change 100755 => 100644 arch/arm/mach-rk29/board-rk29-phonesdk.c mode change 100755 => 100644 arch/arm/mach-rk29/devices.c mode change 100755 => 100644 arch/arm/mach-rk29/devices.h create mode 100644 drivers/watchdog/rk29_wdt.c diff --git a/arch/arm/mach-rk29/board-rk29-ddr3sdk.c b/arch/arm/mach-rk29/board-rk29-ddr3sdk.c index c916b0ba7a8b..4fb9faf0dbd3 100755 --- a/arch/arm/mach-rk29/board-rk29-ddr3sdk.c +++ b/arch/arm/mach-rk29/board-rk29-ddr3sdk.c @@ -1318,6 +1318,11 @@ static void __init rk29_board_iomux_init(void) } static struct platform_device *devices[] __initdata = { + +#ifdef CONFIG_RK29_WATCHDOG + &rk29_device_wdt, +#endif + #ifdef CONFIG_UART1_RK29 &rk29_device_uart1, #endif diff --git a/arch/arm/mach-rk29/board-rk29-phonesdk.c b/arch/arm/mach-rk29/board-rk29-phonesdk.c old mode 100755 new mode 100644 index 256664c46017..ea480afb353f --- a/arch/arm/mach-rk29/board-rk29-phonesdk.c +++ b/arch/arm/mach-rk29/board-rk29-phonesdk.c @@ -2557,6 +2557,11 @@ static void __init rk29_board_iomux_init(void) } static struct platform_device *devices[] __initdata = { + +#ifdef CONFIG_RK29_WATCHDOG + &rk29_device_wdt, +#endif + #ifdef CONFIG_UART1_RK29 &rk29_device_uart1, #endif diff --git a/arch/arm/mach-rk29/board-rk29phonepadsdk.c b/arch/arm/mach-rk29/board-rk29phonepadsdk.c index df490af41c5e..63eb59c91973 100644 --- a/arch/arm/mach-rk29/board-rk29phonepadsdk.c +++ b/arch/arm/mach-rk29/board-rk29phonepadsdk.c @@ -1374,6 +1374,11 @@ static void __init rk29_board_iomux_init(void) } static struct platform_device *devices[] __initdata = { + +#ifdef CONFIG_RK29_WATCHDOG + &rk29_device_wdt, +#endif + #ifdef CONFIG_UART1_RK29 &rk29_device_uart1, #endif diff --git a/arch/arm/mach-rk29/board-rk29sdk.c b/arch/arm/mach-rk29/board-rk29sdk.c index 5cb5c67cfd2a..a9867898e0cc 100755 --- a/arch/arm/mach-rk29/board-rk29sdk.c +++ b/arch/arm/mach-rk29/board-rk29sdk.c @@ -1323,6 +1323,11 @@ static void __init rk29_board_iomux_init(void) } static struct platform_device *devices[] __initdata = { + +#ifdef CONFIG_RK29_WATCHDOG + &rk29_device_wdt, +#endif + #ifdef CONFIG_UART1_RK29 &rk29_device_uart1, #endif diff --git a/arch/arm/mach-rk29/devices.c b/arch/arm/mach-rk29/devices.c old mode 100755 new mode 100644 index 5e6788919120..364079e73c17 --- a/arch/arm/mach-rk29/devices.c +++ b/arch/arm/mach-rk29/devices.c @@ -283,6 +283,36 @@ struct platform_device rk29_device_sdmmc1 = { }, }; #endif + +/* + * rk29 wdt device ADDED BY HHB@ROCK-CHIPS.COM + */ + +#ifdef CONFIG_RK29_WATCHDOG + +static struct resource resources_wdt[] = { + { + .start = IRQ_WDT, + .end = IRQ_WDT, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK29_WDT_PHYS, + .end = RK29_WDT_PHYS + RK29_WDT_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device rk29_device_wdt = { + .name = "rk29-wdt", + .id = 0, + .num_resources = ARRAY_SIZE(resources_wdt), + .resource = resources_wdt, +}; + +#endif + + /* * rk29 4 uarts device */ diff --git a/arch/arm/mach-rk29/devices.h b/arch/arm/mach-rk29/devices.h old mode 100755 new mode 100644 index 8cf367dbd4ab..9c95ab39f6ea --- a/arch/arm/mach-rk29/devices.h +++ b/arch/arm/mach-rk29/devices.h @@ -76,4 +76,6 @@ extern struct platform_device rk29_device_rndis; extern struct platform_device rk29_device_vmac; extern struct rk29_vmac_platform_data rk29_vmac_pdata; extern struct platform_device rk29_device_ipp; +extern struct platform_device rk29_device_wdt; + #endif diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3711b888d482..8c510336896a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -73,6 +73,32 @@ config WM8350_WATCHDOG # ARM Architecture +config RK29_WATCHDOG + tristate "RK29 watchdog" + depends on ARCH_RK29 + help + Watchdog timer embedded into RK29xx chips. This will reboot your + system when the timeout is reached. + +config RK29_FEED_DOG_BY_INTE + bool "feed watchdog by interrupt" + depends on RK29_WATCHDOG + +config RK29_WATCHDOG_ATBOOT + bool "start watchdog at system boot" + depends on RK29_WATCHDOG + +config RK29_WATCHDOG_DEFAULT_TIME + int "set watchdog time out value (unit second)" + depends on RK29_WATCHDOG + help + the real time out value is two times more than the setting value + +config RK29_WATCHDOG_DEBUG + bool "enable watchdog debug" + depends on RK29_WATCHDOG + + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on ARCH_AT91RM9200 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 699199b1baa6..b4de466ace6f 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o +obj-$(CONFIG_RK29_WATCHDOG) += rk29_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/rk29_wdt.c b/drivers/watchdog/rk29_wdt.c new file mode 100644 index 000000000000..c155bdb1dad7 --- /dev/null +++ b/drivers/watchdog/rk29_wdt.c @@ -0,0 +1,510 @@ +/* linux/drivers/watchdog/rk29_wdt.c + * + * Copyright (C) 2011 ROCKCHIP, Inc. + * hhb@rock-chips.com + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* RK29 registers define */ +#define RK29_WDT_CR 0X00 +#define RK29_WDT_TORR 0X04 +#define RK29_WDT_CCVR 0X08 +#define RK29_WDT_CRR 0X0C +#define RK29_WDT_STAT 0X10 +#define RK29_WDT_EOI 0X14 + +#define RK29_WDT_EN 1 +#define RK29_RESPONSE_MODE 1 +#define RK29_RESET_PULSE 4 + +//THAT wdt's clock is 24MHZ +#define RK29_WDT_2730US 0 +#define RK29_WDT_5460US 1 +#define RK29_WDT_10920US 2 +#define RK29_WDT_21840US 3 +#define RK29_WDT_43680US 4 +#define RK29_WDT_87360US 5 +#define RK29_WDT_174720US 6 +#define RK29_WDT_349440US 7 +#define RK29_WDT_698880US 8 +#define RK29_WDT_1397760US 9 +#define RK29_WDT_2795520US 10 +#define RK29_WDT_5591040US 11 +#define RK29_WDT_11182080US 12 +#define RK29_WDT_22364160US 13 +#define RK29_WDT_44728320US 14 +#define RK29_WDT_89456640US 15 + +/* +#define CONFIG_RK29_WATCHDOG_ATBOOT (1) +#define CONFIG_RK29_WATCHDOG_DEFAULT_TIME 10 //unit second +#define CONFIG_RK29_WATCHDOG_DEBUG 1 +*/ + +static int nowayout = WATCHDOG_NOWAYOUT; +static int tmr_margin = CONFIG_RK29_WATCHDOG_DEFAULT_TIME; +#ifdef CONFIG_RK29_WATCHDOG_ATBOOT +static int tmr_atboot = 1; +#else +static int tmr_atboot = 0; +#endif + +static int soft_noboot; + +#ifdef CONFIG_RK29_WATCHDOG_DEBUG +static int debug = 1; +#else +static int debug = 0; +#endif + +module_param(tmr_margin, int, 0); +module_param(tmr_atboot, int, 0); +module_param(nowayout, int, 0); +module_param(soft_noboot, int, 0); +module_param(debug, int, 0); + +MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" + __MODULE_STRING(CONFIG_RK29_WATCHDOG_DEFAULT_TIME) ")"); +MODULE_PARM_DESC(tmr_atboot, + "Watchdog is started at boot time if set to 1, default=" + __MODULE_STRING(CONFIG_RK29_WATCHDOG_ATBOOT)); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, " + "0 to reboot (default depends on ONLY_TESTING)"); +MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); + + +static unsigned long open_lock; +static struct device *wdt_dev; /* platform device attached to */ +static struct resource *wdt_mem; +static struct resource *wdt_irq; +static struct clk *wdt_clock; +static void __iomem *wdt_base; +static unsigned int wdt_count; +static char expect_close; + +static DEFINE_SPINLOCK(wdt_lock); + +/* watchdog control routines */ + +#define DBG(msg...) do { \ + if (debug) \ + printk(KERN_INFO msg); \ + } while (0) + + +/* functions */ +static void rk29_wdt_keepalive(void) +{ + spin_lock(&wdt_lock); + writel(0x76, wdt_base + RK29_WDT_CRR); + spin_unlock(&wdt_lock); +} + +static void __rk29_wdt_stop(void) +{ + unsigned long wtcon = 0; + + wtcon = readl(wdt_base + RK29_WDT_CR); + wtcon &= 0xfffffffe; + writel(wtcon, wdt_base + RK29_WDT_CR); +} + +static void rk29_wdt_stop(void) +{ + spin_lock(&wdt_lock); + __rk29_wdt_stop(); + spin_unlock(&wdt_lock); +} + +static void rk29_wdt_start(void) +{ + unsigned long wtcon = 0; + + spin_lock(&wdt_lock); + __rk29_wdt_stop(); + wtcon |= (RK29_WDT_EN << 0) | (RK29_RESPONSE_MODE << 1) | (RK29_RESET_PULSE << 2); + writel(wtcon, wdt_base + RK29_WDT_CR); + spin_unlock(&wdt_lock); +} +/* timeout unit us */ +static int rk29_wdt_set_heartbeat(int timeout) +{ + unsigned int freq = clk_get_rate(wdt_clock); + unsigned int count; + unsigned int torr = 0; + unsigned int acc = 1; + + if (timeout < 1) + return -EINVAL; + +// freq /= 1000000; + count = timeout * freq; + count /= 0x10000; + + while(acc < count){ + acc *= 2; + torr++; + } + if(torr > 15){ + torr = 15; + } + DBG("%s:%d\n", __func__, torr); + writel(torr, wdt_base + RK29_WDT_TORR); + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int rk29_wdt_open(struct inode *inode, struct file *file) +{ + DBG("%s\n", __func__); + if (test_and_set_bit(0, &open_lock)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + expect_close = 0; + + /* start the timer */ + rk29_wdt_start(); + return nonseekable_open(inode, file); +} + +static int rk29_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + DBG("%s\n", __func__); + if (expect_close == 42) + rk29_wdt_stop(); + else { + dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); + rk29_wdt_keepalive(); + } + expect_close = 0; + clear_bit(0, &open_lock); + return 0; +} + +static ssize_t rk29_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + DBG("%s\n", __func__); + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + rk29_wdt_keepalive(); + } + return len; +} + +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) + +static const struct watchdog_info rk29_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "rk29 Watchdog", +}; + + +static long rk29_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + DBG("%s\n", __func__); + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &rk29_wdt_ident, + sizeof(rk29_wdt_ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + DBG("%s:rk29_wdt_keepalive\n", __func__); + rk29_wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (rk29_wdt_set_heartbeat(new_margin)) + return -EINVAL; + rk29_wdt_keepalive(); + return put_user(tmr_margin, p); + case WDIOC_GETTIMEOUT: + return put_user(tmr_margin, p); + default: + return -ENOTTY; + } +} + + + +/* kernel interface */ + +static const struct file_operations rk29_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = rk29_wdt_write, + .unlocked_ioctl = rk29_wdt_ioctl, + .open = rk29_wdt_open, + .release = rk29_wdt_release, +}; + +static struct miscdevice rk29_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &rk29_wdt_fops, +}; + + +/* interrupt handler code */ + +static irqreturn_t rk29_wdt_irq_handler(int irqno, void *param) +{ + DBG("RK29_wdt:watchdog timer expired (irq)\n"); + rk29_wdt_keepalive(); + return IRQ_HANDLED; +} + + +/* device interface */ + +static int __devinit rk29_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev; + int started = 0; + int ret; + int size; + + dev = &pdev->dev; + wdt_dev = &pdev->dev; + + /* get the memory region for the watchdog timer */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "no memory resource specified\n"); + return -ENOENT; + } + + size = (res->end - res->start) + 1; + wdt_mem = request_mem_region(res->start, size, pdev->name); + if (wdt_mem == NULL) { + dev_err(dev, "failed to get memory region\n"); + ret = -ENOENT; + goto err_req; + } + + wdt_base = ioremap(res->start, size); + if (wdt_base == NULL) { + dev_err(dev, "failed to ioremap() region\n"); + ret = -EINVAL; + goto err_req; + } + + DBG("probe: mapped wdt_base=%p\n", wdt_base); + + +#ifdef CONFIG_RK29_FEED_DOG_BY_INTE + + wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (wdt_irq == NULL) { + dev_err(dev, "no irq resource specified\n"); + ret = -ENOENT; + goto err_map; + } + + ret = request_irq(wdt_irq->start, rk29_wdt_irq_handler, 0, pdev->name, pdev); + + if (ret != 0) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err_map; + } + +#endif + + wdt_clock = clk_get(&pdev->dev, "wdt"); + if (IS_ERR(wdt_clock)) { + dev_err(dev, "failed to find watchdog clock source\n"); + ret = PTR_ERR(wdt_clock); + goto err_irq; + } + + clk_enable(wdt_clock); + + rk29_wdt_set_heartbeat(tmr_margin); + + ret = misc_register(&rk29_wdt_miscdev); + if (ret) { + dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", + WATCHDOG_MINOR, ret); + goto err_clk; + } + printk("watchdog misc directory:%s\n", rk29_wdt_miscdev.nodename); + if (tmr_atboot && started == 0) { + dev_info(dev, "starting watchdog timer\n"); + rk29_wdt_start(); + } else if (!tmr_atboot) { + /* if we're not enabling the watchdog, then ensure it is + * disabled if it has been left running from the bootloader + * or other source */ + + rk29_wdt_stop(); + } + + return 0; + + err_clk: + clk_disable(wdt_clock); + clk_put(wdt_clock); + + err_irq: + free_irq(wdt_irq->start, pdev); + + err_map: + iounmap(wdt_base); + + err_req: + release_resource(wdt_mem); + kfree(wdt_mem); + + return ret; +} + +static int __devexit rk29_wdt_remove(struct platform_device *dev) +{ + release_resource(wdt_mem); + kfree(wdt_mem); + wdt_mem = NULL; + + free_irq(wdt_irq->start, dev); + wdt_irq = NULL; + + clk_disable(wdt_clock); + clk_put(wdt_clock); + wdt_clock = NULL; + + iounmap(wdt_base); + misc_deregister(&rk29_wdt_miscdev); + + return 0; +} + +static void rk29_wdt_shutdown(struct platform_device *dev) +{ + rk29_wdt_stop(); +} + +#ifdef CONFIG_PM + +static int rk29_wdt_suspend(struct platform_device *dev, pm_message_t state) +{ + rk29_wdt_stop(); + return 0; +} + +static int rk29_wdt_resume(struct platform_device *dev) +{ + rk29_wdt_set_heartbeat(tmr_margin); + rk29_wdt_start(); + return 0; +} + +#else +#define rk29_wdt_suspend NULL +#define rk29_wdt_resume NULL +#endif /* CONFIG_PM */ + + +static struct platform_driver rk29_wdt_driver = { + .probe = rk29_wdt_probe, + .remove = __devexit_p(rk29_wdt_remove), + .shutdown = rk29_wdt_shutdown, + .suspend = rk29_wdt_suspend, + .resume = rk29_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "rk29-wdt", + }, +}; + + +static char banner[] __initdata = + KERN_INFO "RK29 Watchdog Timer, (c) 2011 Rockchip Electronics\n"; + +static int __init watchdog_init(void) +{ + printk(banner); + return platform_driver_register(&rk29_wdt_driver); +} + +static void __exit watchdog_exit(void) +{ + platform_driver_unregister(&rk29_wdt_driver); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("hhb@rock-chips.com"); +MODULE_DESCRIPTION("RK29 Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:rk29-wdt"); -- 2.34.1