1 /* 2 * drivers/char/watchdog/pnx4008_wdt.c 3 * 4 * Watchdog driver for PNX4008 board 5 * 6 * Authors: Dmitry Chigirev <source@mvista.com>, 7 * Vitaly Wool <vitalywool@gmail.com> 8 * Based on sa1100 driver, 9 * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> 10 * 11 * 2005-2006 (c) MontaVista Software, Inc. 12 * 13 * (C) 2012 Wolfram Sang, Pengutronix 14 * 15 * This file is licensed under the terms of the GNU General Public License 16 * version 2. This program is licensed "as is" without any warranty of any 17 * kind, whether express or implied. 18 */ 19 20 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 21 22 #include <linux/module.h> 23 #include <linux/moduleparam.h> 24 #include <linux/types.h> 25 #include <linux/kernel.h> 26 #include <linux/watchdog.h> 27 #include <linux/platform_device.h> 28 #include <linux/clk.h> 29 #include <linux/spinlock.h> 30 #include <linux/io.h> 31 #include <linux/slab.h> 32 #include <linux/err.h> 33 #include <linux/of.h> 34 #include <mach/hardware.h> 35 36 /* WatchDog Timer - Chapter 23 Page 207 */ 37 38 #define DEFAULT_HEARTBEAT 19 39 #define MAX_HEARTBEAT 60 40 41 /* Watchdog timer register set definition */ 42 #define WDTIM_INT(p) ((p) + 0x0) 43 #define WDTIM_CTRL(p) ((p) + 0x4) 44 #define WDTIM_COUNTER(p) ((p) + 0x8) 45 #define WDTIM_MCTRL(p) ((p) + 0xC) 46 #define WDTIM_MATCH0(p) ((p) + 0x10) 47 #define WDTIM_EMR(p) ((p) + 0x14) 48 #define WDTIM_PULSE(p) ((p) + 0x18) 49 #define WDTIM_RES(p) ((p) + 0x1C) 50 51 /* WDTIM_INT bit definitions */ 52 #define MATCH_INT 1 53 54 /* WDTIM_CTRL bit definitions */ 55 #define COUNT_ENAB 1 56 #define RESET_COUNT (1 << 1) 57 #define DEBUG_EN (1 << 2) 58 59 /* WDTIM_MCTRL bit definitions */ 60 #define MR0_INT 1 61 #undef RESET_COUNT0 62 #define RESET_COUNT0 (1 << 2) 63 #define STOP_COUNT0 (1 << 2) 64 #define M_RES1 (1 << 3) 65 #define M_RES2 (1 << 4) 66 #define RESFRC1 (1 << 5) 67 #define RESFRC2 (1 << 6) 68 69 /* WDTIM_EMR bit definitions */ 70 #define EXT_MATCH0 1 71 #define MATCH_OUTPUT_HIGH (2 << 4) /*a MATCH_CTRL setting */ 72 73 /* WDTIM_RES bit definitions */ 74 #define WDOG_RESET 1 /* read only */ 75 76 #define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ 77 78 static bool nowayout = WATCHDOG_NOWAYOUT; 79 static unsigned int heartbeat = DEFAULT_HEARTBEAT; 80 81 static DEFINE_SPINLOCK(io_lock); 82 static void __iomem *wdt_base; 83 struct clk *wdt_clk; 84 85 static int pnx4008_wdt_start(struct watchdog_device *wdd) 86 { 87 spin_lock(&io_lock); 88 89 /* stop counter, initiate counter reset */ 90 writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); 91 /*wait for reset to complete. 100% guarantee event */ 92 while (readl(WDTIM_COUNTER(wdt_base))) 93 cpu_relax(); 94 /* internal and external reset, stop after that */ 95 writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base)); 96 /* configure match output */ 97 writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); 98 /* clear interrupt, just in case */ 99 writel(MATCH_INT, WDTIM_INT(wdt_base)); 100 /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ 101 writel(0xFFFF, WDTIM_PULSE(wdt_base)); 102 writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); 103 /*enable counter, stop when debugger active */ 104 writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); 105 106 spin_unlock(&io_lock); 107 return 0; 108 } 109 110 static int pnx4008_wdt_stop(struct watchdog_device *wdd) 111 { 112 spin_lock(&io_lock); 113 114 writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ 115 116 spin_unlock(&io_lock); 117 return 0; 118 } 119 120 static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, 121 unsigned int new_timeout) 122 { 123 wdd->timeout = new_timeout; 124 return 0; 125 } 126 127 static const struct watchdog_info pnx4008_wdt_ident = { 128 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 129 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 130 .identity = "PNX4008 Watchdog", 131 }; 132 133 static const struct watchdog_ops pnx4008_wdt_ops = { 134 .owner = THIS_MODULE, 135 .start = pnx4008_wdt_start, 136 .stop = pnx4008_wdt_stop, 137 .set_timeout = pnx4008_wdt_set_timeout, 138 }; 139 140 static struct watchdog_device pnx4008_wdd = { 141 .info = &pnx4008_wdt_ident, 142 .ops = &pnx4008_wdt_ops, 143 .timeout = DEFAULT_HEARTBEAT, 144 .min_timeout = 1, 145 .max_timeout = MAX_HEARTBEAT, 146 }; 147 148 static int pnx4008_wdt_probe(struct platform_device *pdev) 149 { 150 struct resource *r; 151 int ret = 0; 152 153 watchdog_init_timeout(&pnx4008_wdd, heartbeat, &pdev->dev); 154 155 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 156 wdt_base = devm_ioremap_resource(&pdev->dev, r); 157 if (IS_ERR(wdt_base)) 158 return PTR_ERR(wdt_base); 159 160 wdt_clk = devm_clk_get(&pdev->dev, NULL); 161 if (IS_ERR(wdt_clk)) 162 return PTR_ERR(wdt_clk); 163 164 ret = clk_enable(wdt_clk); 165 if (ret) 166 return ret; 167 168 pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? 169 WDIOF_CARDRESET : 0; 170 watchdog_set_nowayout(&pnx4008_wdd, nowayout); 171 172 pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */ 173 174 ret = watchdog_register_device(&pnx4008_wdd); 175 if (ret < 0) { 176 dev_err(&pdev->dev, "cannot register watchdog device\n"); 177 goto disable_clk; 178 } 179 180 dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", 181 pnx4008_wdd.timeout); 182 183 return 0; 184 185 disable_clk: 186 clk_disable(wdt_clk); 187 return ret; 188 } 189 190 static int pnx4008_wdt_remove(struct platform_device *pdev) 191 { 192 watchdog_unregister_device(&pnx4008_wdd); 193 194 clk_disable(wdt_clk); 195 196 return 0; 197 } 198 199 #ifdef CONFIG_OF 200 static const struct of_device_id pnx4008_wdt_match[] = { 201 { .compatible = "nxp,pnx4008-wdt" }, 202 { } 203 }; 204 MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); 205 #endif 206 207 static struct platform_driver platform_wdt_driver = { 208 .driver = { 209 .name = "pnx4008-watchdog", 210 .of_match_table = of_match_ptr(pnx4008_wdt_match), 211 }, 212 .probe = pnx4008_wdt_probe, 213 .remove = pnx4008_wdt_remove, 214 }; 215 216 module_platform_driver(platform_wdt_driver); 217 218 MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 219 MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); 220 MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 221 222 module_param(heartbeat, uint, 0); 223 MODULE_PARM_DESC(heartbeat, 224 "Watchdog heartbeat period in seconds from 1 to " 225 __MODULE_STRING(MAX_HEARTBEAT) ", default " 226 __MODULE_STRING(DEFAULT_HEARTBEAT)); 227 228 module_param(nowayout, bool, 0); 229 MODULE_PARM_DESC(nowayout, 230 "Set to 1 to keep watchdog running after device release"); 231 232 MODULE_LICENSE("GPL"); 233 MODULE_ALIAS("platform:pnx4008-watchdog"); 234