pnx4008_wdt.c (6938B)
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 76static bool nowayout = WATCHDOG_NOWAYOUT; 77static unsigned int heartbeat; 78 79static DEFINE_SPINLOCK(io_lock); 80static void __iomem *wdt_base; 81static struct clk *wdt_clk; 82 83static 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 108static 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 118static 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 125static 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 160static const struct watchdog_info pnx4008_wdt_ident = { 161 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 162 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 163 .identity = "PNX4008 Watchdog", 164}; 165 166static 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 174static 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 182static void pnx4008_clk_disable_unprepare(void *data) 183{ 184 clk_disable_unprepare(data); 185} 186 187static 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 229static const struct of_device_id pnx4008_wdt_match[] = { 230 { .compatible = "nxp,pnx4008-wdt" }, 231 { } 232}; 233MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); 234#endif 235 236static 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 244module_platform_driver(platform_wdt_driver); 245 246MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 247MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>"); 248MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 249 250module_param(heartbeat, uint, 0); 251MODULE_PARM_DESC(heartbeat, 252 "Watchdog heartbeat period in seconds from 1 to " 253 __MODULE_STRING(MAX_HEARTBEAT) ", default " 254 __MODULE_STRING(DEFAULT_HEARTBEAT)); 255 256module_param(nowayout, bool, 0); 257MODULE_PARM_DESC(nowayout, 258 "Set to 1 to keep watchdog running after device release"); 259 260MODULE_LICENSE("GPL"); 261MODULE_ALIAS("platform:pnx4008-watchdog");