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