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 78static bool nowayout = WATCHDOG_NOWAYOUT; 79static unsigned int heartbeat = DEFAULT_HEARTBEAT; 80 81static DEFINE_SPINLOCK(io_lock); 82static void __iomem *wdt_base; 83struct clk *wdt_clk; 84 85static 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 110static 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 120static 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 127static const struct watchdog_info pnx4008_wdt_ident = { 128 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 129 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 130 .identity = "PNX4008 Watchdog", 131}; 132 133static 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 140static 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 148static 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 185disable_clk: 186 clk_disable(wdt_clk); 187 return ret; 188} 189 190static 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 200static const struct of_device_id pnx4008_wdt_match[] = { 201 { .compatible = "nxp,pnx4008-wdt" }, 202 { } 203}; 204MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); 205#endif 206 207static 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 216module_platform_driver(platform_wdt_driver); 217 218MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 219MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>"); 220MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 221 222module_param(heartbeat, uint, 0); 223MODULE_PARM_DESC(heartbeat, 224 "Watchdog heartbeat period in seconds from 1 to " 225 __MODULE_STRING(MAX_HEARTBEAT) ", default " 226 __MODULE_STRING(DEFAULT_HEARTBEAT)); 227 228module_param(nowayout, bool, 0); 229MODULE_PARM_DESC(nowayout, 230 "Set to 1 to keep watchdog running after device release"); 231 232MODULE_LICENSE("GPL"); 233MODULE_ALIAS("platform:pnx4008-watchdog"); 234