/* * drivers/char/watchdog/pnx4008_wdt.c * * Watchdog driver for PNX4008 board * * Authors: Dmitry Chigirev <source@mvista.com>, * Vitaly Wool <vitalywool@gmail.com> * Based on sa1100 driver, * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> * * 2005-2006 (c) MontaVista Software, Inc. * * (C) 2012 Wolfram Sang, Pengutronix * * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/spinlock.h> #include <linux/io.h> #include <linux/slab.h> #include <linux/err.h> #include <linux/of.h> #include <mach/hardware.h> /* WatchDog Timer - Chapter 23 Page 207 */ #define DEFAULT_HEARTBEAT 19 #define MAX_HEARTBEAT 60 /* Watchdog timer register set definition */ #define WDTIM_INT(p) ((p) + 0x0) #define WDTIM_CTRL(p) ((p) + 0x4) #define WDTIM_COUNTER(p) ((p) + 0x8) #define WDTIM_MCTRL(p) ((p) + 0xC) #define WDTIM_MATCH0(p) ((p) + 0x10) #define WDTIM_EMR(p) ((p) + 0x14) #define WDTIM_PULSE(p) ((p) + 0x18) #define WDTIM_RES(p) ((p) + 0x1C) /* WDTIM_INT bit definitions */ #define MATCH_INT 1 /* WDTIM_CTRL bit definitions */ #define COUNT_ENAB 1 #define RESET_COUNT (1 << 1) #define DEBUG_EN (1 << 2) /* WDTIM_MCTRL bit definitions */ #define MR0_INT 1 #undef RESET_COUNT0 #define RESET_COUNT0 (1 << 2) #define STOP_COUNT0 (1 << 2) #define M_RES1 (1 << 3) #define M_RES2 (1 << 4) #define RESFRC1 (1 << 5) #define RESFRC2 (1 << 6) /* WDTIM_EMR bit definitions */ #define EXT_MATCH0 1 #define MATCH_OUTPUT_HIGH (2 << 4) /*a MATCH_CTRL setting */ /* WDTIM_RES bit definitions */ #define WDOG_RESET 1 /* read only */ #define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ static bool nowayout = WATCHDOG_NOWAYOUT; static unsigned int heartbeat = DEFAULT_HEARTBEAT; static DEFINE_SPINLOCK(io_lock); static void __iomem *wdt_base; struct clk *wdt_clk; static int pnx4008_wdt_start(struct watchdog_device *wdd) { spin_lock(&io_lock); /* stop counter, initiate counter reset */ writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); /*wait for reset to complete. 100% guarantee event */ while (readl(WDTIM_COUNTER(wdt_base))) cpu_relax(); /* internal and external reset, stop after that */ writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base)); /* configure match output */ writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); /* clear interrupt, just in case */ writel(MATCH_INT, WDTIM_INT(wdt_base)); /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ writel(0xFFFF, WDTIM_PULSE(wdt_base)); writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); /*enable counter, stop when debugger active */ writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); spin_unlock(&io_lock); return 0; } static int pnx4008_wdt_stop(struct watchdog_device *wdd) { spin_lock(&io_lock); writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ spin_unlock(&io_lock); return 0; } static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout) { wdd->timeout = new_timeout; return 0; } static const struct watchdog_info pnx4008_wdt_ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, .identity = "PNX4008 Watchdog", }; static const struct watchdog_ops pnx4008_wdt_ops = { .owner = THIS_MODULE, .start = pnx4008_wdt_start, .stop = pnx4008_wdt_stop, .set_timeout = pnx4008_wdt_set_timeout, }; static struct watchdog_device pnx4008_wdd = { .info = &pnx4008_wdt_ident, .ops = &pnx4008_wdt_ops, .timeout = DEFAULT_HEARTBEAT, .min_timeout = 1, .max_timeout = MAX_HEARTBEAT, }; static int pnx4008_wdt_probe(struct platform_device *pdev) { struct resource *r; int ret = 0; watchdog_init_timeout(&pnx4008_wdd, heartbeat, &pdev->dev); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); wdt_base = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(wdt_base)) return PTR_ERR(wdt_base); wdt_clk = clk_get(&pdev->dev, NULL); if (IS_ERR(wdt_clk)) return PTR_ERR(wdt_clk); ret = clk_enable(wdt_clk); if (ret) goto out; pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? WDIOF_CARDRESET : 0; watchdog_set_nowayout(&pnx4008_wdd, nowayout); pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */ ret = watchdog_register_device(&pnx4008_wdd); if (ret < 0) { dev_err(&pdev->dev, "cannot register watchdog device\n"); goto disable_clk; } dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", pnx4008_wdd.timeout); return 0; disable_clk: clk_disable(wdt_clk); out: clk_put(wdt_clk); return ret; } static int pnx4008_wdt_remove(struct platform_device *pdev) { watchdog_unregister_device(&pnx4008_wdd); clk_disable(wdt_clk); clk_put(wdt_clk); return 0; } #ifdef CONFIG_OF static const struct of_device_id pnx4008_wdt_match[] = { { .compatible = "nxp,pnx4008-wdt" }, { } }; MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); #endif static struct platform_driver platform_wdt_driver = { .driver = { .name = "pnx4008-watchdog", .owner = THIS_MODULE, .of_match_table = of_match_ptr(pnx4008_wdt_match), }, .probe = pnx4008_wdt_probe, .remove = pnx4008_wdt_remove, }; module_platform_driver(platform_wdt_driver); MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); module_param(heartbeat, uint, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds from 1 to " __MODULE_STRING(MAX_HEARTBEAT) ", default " __MODULE_STRING(DEFAULT_HEARTBEAT)); module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Set to 1 to keep watchdog running after device release"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:pnx4008-watchdog");