| /* |
| * 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, |
| .min_timeout = 1, |
| .max_timeout = MAX_HEARTBEAT, |
| }; |
| |
| static int __devinit pnx4008_wdt_probe(struct platform_device *pdev) |
| { |
| struct resource *r; |
| int ret = 0; |
| |
| if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) |
| heartbeat = DEFAULT_HEARTBEAT; |
| |
| r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| wdt_base = devm_request_and_ioremap(&pdev->dev, r); |
| if (!wdt_base) |
| return -EADDRINUSE; |
| |
| 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.timeout = heartbeat; |
| 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", |
| heartbeat); |
| |
| return 0; |
| |
| disable_clk: |
| clk_disable(wdt_clk); |
| out: |
| clk_put(wdt_clk); |
| return ret; |
| } |
| |
| static int __devexit 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"); |