sp805_wdt.c revision 8deea830644c0a4bdd90dfda7249d0f40f0667fa
1/* 2 * drivers/char/watchdog/sp805-wdt.c 3 * 4 * Watchdog driver for ARM SP805 watchdog module 5 * 6 * Copyright (C) 2010 ST Microelectronics 7 * Viresh Kumar <viresh.linux@gmail.com> 8 * 9 * This file is licensed under the terms of the GNU General Public 10 * License version 2 or later. This program is licensed "as is" without any 11 * warranty of any kind, whether express or implied. 12 */ 13 14#include <linux/device.h> 15#include <linux/resource.h> 16#include <linux/amba/bus.h> 17#include <linux/bitops.h> 18#include <linux/clk.h> 19#include <linux/io.h> 20#include <linux/ioport.h> 21#include <linux/kernel.h> 22#include <linux/math64.h> 23#include <linux/module.h> 24#include <linux/moduleparam.h> 25#include <linux/pm.h> 26#include <linux/slab.h> 27#include <linux/spinlock.h> 28#include <linux/types.h> 29#include <linux/watchdog.h> 30 31/* default timeout in seconds */ 32#define DEFAULT_TIMEOUT 60 33 34#define MODULE_NAME "sp805-wdt" 35 36/* watchdog register offsets and masks */ 37#define WDTLOAD 0x000 38 #define LOAD_MIN 0x00000001 39 #define LOAD_MAX 0xFFFFFFFF 40#define WDTVALUE 0x004 41#define WDTCONTROL 0x008 42 /* control register masks */ 43 #define INT_ENABLE (1 << 0) 44 #define RESET_ENABLE (1 << 1) 45#define WDTINTCLR 0x00C 46#define WDTRIS 0x010 47#define WDTMIS 0x014 48 #define INT_MASK (1 << 0) 49#define WDTLOCK 0xC00 50 #define UNLOCK 0x1ACCE551 51 #define LOCK 0x00000001 52 53/** 54 * struct sp805_wdt: sp805 wdt device structure 55 * @wdd: instance of struct watchdog_device 56 * @lock: spin lock protecting dev structure and io access 57 * @base: base address of wdt 58 * @clk: clock structure of wdt 59 * @adev: amba device structure of wdt 60 * @status: current status of wdt 61 * @load_val: load value to be set for current timeout 62 * @timeout: current programmed timeout 63 */ 64struct sp805_wdt { 65 struct watchdog_device wdd; 66 spinlock_t lock; 67 void __iomem *base; 68 struct clk *clk; 69 struct amba_device *adev; 70 unsigned int load_val; 71 unsigned int timeout; 72}; 73 74static bool nowayout = WATCHDOG_NOWAYOUT; 75module_param(nowayout, bool, 0); 76MODULE_PARM_DESC(nowayout, 77 "Set to 1 to keep watchdog running after device release"); 78 79/* This routine finds load value that will reset system in required timout */ 80static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout) 81{ 82 struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); 83 u64 load, rate; 84 85 rate = clk_get_rate(wdt->clk); 86 87 /* 88 * sp805 runs counter with given value twice, after the end of first 89 * counter it gives an interrupt and then starts counter again. If 90 * interrupt already occurred then it resets the system. This is why 91 * load is half of what should be required. 92 */ 93 load = div_u64(rate, 2) * timeout - 1; 94 95 load = (load > LOAD_MAX) ? LOAD_MAX : load; 96 load = (load < LOAD_MIN) ? LOAD_MIN : load; 97 98 spin_lock(&wdt->lock); 99 wdt->load_val = load; 100 /* roundup timeout to closest positive integer value */ 101 wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); 102 spin_unlock(&wdt->lock); 103 104 return 0; 105} 106 107/* returns number of seconds left for reset to occur */ 108static unsigned int wdt_timeleft(struct watchdog_device *wdd) 109{ 110 struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); 111 u64 load, rate; 112 113 rate = clk_get_rate(wdt->clk); 114 115 spin_lock(&wdt->lock); 116 load = readl_relaxed(wdt->base + WDTVALUE); 117 118 /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */ 119 if (!(readl_relaxed(wdt->base + WDTRIS) & INT_MASK)) 120 load += wdt->load_val + 1; 121 spin_unlock(&wdt->lock); 122 123 return div_u64(load, rate); 124} 125 126static int wdt_config(struct watchdog_device *wdd, bool ping) 127{ 128 struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); 129 int ret; 130 131 if (!ping) { 132 133 ret = clk_prepare_enable(wdt->clk); 134 if (ret) { 135 dev_err(&wdt->adev->dev, "clock enable fail"); 136 return ret; 137 } 138 } 139 140 spin_lock(&wdt->lock); 141 142 writel_relaxed(UNLOCK, wdt->base + WDTLOCK); 143 writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); 144 145 if (!ping) { 146 writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); 147 writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + 148 WDTCONTROL); 149 } 150 151 writel_relaxed(LOCK, wdt->base + WDTLOCK); 152 153 /* Flush posted writes. */ 154 readl_relaxed(wdt->base + WDTLOCK); 155 spin_unlock(&wdt->lock); 156 157 return 0; 158} 159 160static int wdt_ping(struct watchdog_device *wdd) 161{ 162 return wdt_config(wdd, true); 163} 164 165/* enables watchdog timers reset */ 166static int wdt_enable(struct watchdog_device *wdd) 167{ 168 return wdt_config(wdd, false); 169} 170 171/* disables watchdog timers reset */ 172static int wdt_disable(struct watchdog_device *wdd) 173{ 174 struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); 175 176 spin_lock(&wdt->lock); 177 178 writel_relaxed(UNLOCK, wdt->base + WDTLOCK); 179 writel_relaxed(0, wdt->base + WDTCONTROL); 180 writel_relaxed(LOCK, wdt->base + WDTLOCK); 181 182 /* Flush posted writes. */ 183 readl_relaxed(wdt->base + WDTLOCK); 184 spin_unlock(&wdt->lock); 185 186 clk_disable_unprepare(wdt->clk); 187 188 return 0; 189} 190 191static const struct watchdog_info wdt_info = { 192 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 193 .identity = MODULE_NAME, 194}; 195 196static const struct watchdog_ops wdt_ops = { 197 .owner = THIS_MODULE, 198 .start = wdt_enable, 199 .stop = wdt_disable, 200 .ping = wdt_ping, 201 .set_timeout = wdt_setload, 202 .get_timeleft = wdt_timeleft, 203}; 204 205static int 206sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) 207{ 208 struct sp805_wdt *wdt; 209 int ret = 0; 210 211 if (!devm_request_mem_region(&adev->dev, adev->res.start, 212 resource_size(&adev->res), "sp805_wdt")) { 213 dev_warn(&adev->dev, "Failed to get memory region resource\n"); 214 ret = -ENOENT; 215 goto err; 216 } 217 218 wdt = devm_kzalloc(&adev->dev, sizeof(*wdt), GFP_KERNEL); 219 if (!wdt) { 220 ret = -ENOMEM; 221 goto err; 222 } 223 224 wdt->base = devm_ioremap(&adev->dev, adev->res.start, 225 resource_size(&adev->res)); 226 if (!wdt->base) { 227 ret = -ENOMEM; 228 dev_warn(&adev->dev, "ioremap fail\n"); 229 goto err; 230 } 231 232 wdt->clk = devm_clk_get(&adev->dev, NULL); 233 if (IS_ERR(wdt->clk)) { 234 dev_warn(&adev->dev, "Clock not found\n"); 235 ret = PTR_ERR(wdt->clk); 236 goto err; 237 } 238 239 wdt->adev = adev; 240 wdt->wdd.info = &wdt_info; 241 wdt->wdd.ops = &wdt_ops; 242 243 spin_lock_init(&wdt->lock); 244 watchdog_set_nowayout(&wdt->wdd, nowayout); 245 watchdog_set_drvdata(&wdt->wdd, wdt); 246 wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); 247 248 ret = watchdog_register_device(&wdt->wdd); 249 if (ret) { 250 dev_err(&adev->dev, "watchdog_register_device() failed: %d\n", 251 ret); 252 goto err; 253 } 254 amba_set_drvdata(adev, wdt); 255 256 dev_info(&adev->dev, "registration successful\n"); 257 return 0; 258 259err: 260 dev_err(&adev->dev, "Probe Failed!!!\n"); 261 return ret; 262} 263 264static int sp805_wdt_remove(struct amba_device *adev) 265{ 266 struct sp805_wdt *wdt = amba_get_drvdata(adev); 267 268 watchdog_unregister_device(&wdt->wdd); 269 watchdog_set_drvdata(&wdt->wdd, NULL); 270 271 return 0; 272} 273 274static int __maybe_unused sp805_wdt_suspend(struct device *dev) 275{ 276 struct sp805_wdt *wdt = dev_get_drvdata(dev); 277 278 if (watchdog_active(&wdt->wdd)) 279 return wdt_disable(&wdt->wdd); 280 281 return 0; 282} 283 284static int __maybe_unused sp805_wdt_resume(struct device *dev) 285{ 286 struct sp805_wdt *wdt = dev_get_drvdata(dev); 287 288 if (watchdog_active(&wdt->wdd)) 289 return wdt_enable(&wdt->wdd); 290 291 return 0; 292} 293 294static SIMPLE_DEV_PM_OPS(sp805_wdt_dev_pm_ops, sp805_wdt_suspend, 295 sp805_wdt_resume); 296 297static struct amba_id sp805_wdt_ids[] = { 298 { 299 .id = 0x00141805, 300 .mask = 0x00ffffff, 301 }, 302 { 0, 0 }, 303}; 304 305MODULE_DEVICE_TABLE(amba, sp805_wdt_ids); 306 307static struct amba_driver sp805_wdt_driver = { 308 .drv = { 309 .name = MODULE_NAME, 310 .pm = &sp805_wdt_dev_pm_ops, 311 }, 312 .id_table = sp805_wdt_ids, 313 .probe = sp805_wdt_probe, 314 .remove = sp805_wdt_remove, 315}; 316 317module_amba_driver(sp805_wdt_driver); 318 319MODULE_AUTHOR("Viresh Kumar <viresh.linux@gmail.com>"); 320MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); 321MODULE_LICENSE("GPL"); 322