Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.6 311 lines 8.1 kB view raw
1/* 2 * Copyright 2010-2011 Picochip Ltd., Jamie Iles 3 * http://www.picochip.com 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 8 * 2 of the License, or (at your option) any later version. 9 * 10 * This file implements a driver for the Synopsys DesignWare watchdog device 11 * in the many subsystems. The watchdog has 16 different timeout periods 12 * and these are a function of the input clock frequency. 13 * 14 * The DesignWare watchdog cannot be stopped once it has been started so we 15 * do not implement a stop function. The watchdog core will continue to send 16 * heartbeat requests after the watchdog device has been closed. 17 */ 18 19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 21#include <linux/bitops.h> 22#include <linux/clk.h> 23#include <linux/delay.h> 24#include <linux/err.h> 25#include <linux/io.h> 26#include <linux/kernel.h> 27#include <linux/module.h> 28#include <linux/moduleparam.h> 29#include <linux/notifier.h> 30#include <linux/of.h> 31#include <linux/pm.h> 32#include <linux/platform_device.h> 33#include <linux/reboot.h> 34#include <linux/watchdog.h> 35 36#define WDOG_CONTROL_REG_OFFSET 0x00 37#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 38#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 39#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4 40#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 41#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c 42#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 43 44/* The maximum TOP (timeout period) value that can be set in the watchdog. */ 45#define DW_WDT_MAX_TOP 15 46 47#define DW_WDT_DEFAULT_SECONDS 30 48 49static bool nowayout = WATCHDOG_NOWAYOUT; 50module_param(nowayout, bool, 0); 51MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 52 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 53 54struct dw_wdt { 55 void __iomem *regs; 56 struct clk *clk; 57 struct notifier_block restart_handler; 58 struct watchdog_device wdd; 59}; 60 61#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd) 62 63static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt) 64{ 65 return readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) & 66 WDOG_CONTROL_REG_WDT_EN_MASK; 67} 68 69static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top) 70{ 71 /* 72 * There are 16 possible timeout values in 0..15 where the number of 73 * cycles is 2 ^ (16 + i) and the watchdog counts down. 74 */ 75 return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk); 76} 77 78static int dw_wdt_get_top(struct dw_wdt *dw_wdt) 79{ 80 int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; 81 82 return dw_wdt_top_in_seconds(dw_wdt, top); 83} 84 85static int dw_wdt_ping(struct watchdog_device *wdd) 86{ 87 struct dw_wdt *dw_wdt = to_dw_wdt(wdd); 88 89 writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs + 90 WDOG_COUNTER_RESTART_REG_OFFSET); 91 92 return 0; 93} 94 95static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s) 96{ 97 struct dw_wdt *dw_wdt = to_dw_wdt(wdd); 98 int i, top_val = DW_WDT_MAX_TOP; 99 100 /* 101 * Iterate over the timeout values until we find the closest match. We 102 * always look for >=. 103 */ 104 for (i = 0; i <= DW_WDT_MAX_TOP; ++i) 105 if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) { 106 top_val = i; 107 break; 108 } 109 110 /* 111 * Set the new value in the watchdog. Some versions of dw_wdt 112 * have have TOPINIT in the TIMEOUT_RANGE register (as per 113 * CP_WDT_DUAL_TOP in WDT_COMP_PARAMS_1). On those we 114 * effectively get a pat of the watchdog right here. 115 */ 116 writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT, 117 dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); 118 119 wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val); 120 121 return 0; 122} 123 124static int dw_wdt_start(struct watchdog_device *wdd) 125{ 126 struct dw_wdt *dw_wdt = to_dw_wdt(wdd); 127 128 dw_wdt_set_timeout(wdd, wdd->timeout); 129 130 set_bit(WDOG_HW_RUNNING, &wdd->status); 131 132 writel(WDOG_CONTROL_REG_WDT_EN_MASK, 133 dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); 134 135 return 0; 136} 137 138static int dw_wdt_restart_handle(struct notifier_block *this, 139 unsigned long mode, void *cmd) 140{ 141 struct dw_wdt *dw_wdt; 142 u32 val; 143 144 dw_wdt = container_of(this, struct dw_wdt, restart_handler); 145 146 writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); 147 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); 148 if (val & WDOG_CONTROL_REG_WDT_EN_MASK) 149 writel(WDOG_COUNTER_RESTART_KICK_VALUE, 150 dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); 151 else 152 writel(WDOG_CONTROL_REG_WDT_EN_MASK, 153 dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); 154 155 /* wait for reset to assert... */ 156 mdelay(500); 157 158 return NOTIFY_DONE; 159} 160 161static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd) 162{ 163 struct dw_wdt *dw_wdt = to_dw_wdt(wdd); 164 165 return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) / 166 clk_get_rate(dw_wdt->clk); 167} 168 169static const struct watchdog_info dw_wdt_ident = { 170 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | 171 WDIOF_MAGICCLOSE, 172 .identity = "Synopsys DesignWare Watchdog", 173}; 174 175static const struct watchdog_ops dw_wdt_ops = { 176 .owner = THIS_MODULE, 177 .start = dw_wdt_start, 178 .ping = dw_wdt_ping, 179 .set_timeout = dw_wdt_set_timeout, 180 .get_timeleft = dw_wdt_get_timeleft, 181}; 182 183#ifdef CONFIG_PM_SLEEP 184static int dw_wdt_suspend(struct device *dev) 185{ 186 struct dw_wdt *dw_wdt = dev_get_drvdata(dev); 187 188 clk_disable_unprepare(dw_wdt->clk); 189 190 return 0; 191} 192 193static int dw_wdt_resume(struct device *dev) 194{ 195 struct dw_wdt *dw_wdt = dev_get_drvdata(dev); 196 int err = clk_prepare_enable(dw_wdt->clk); 197 198 if (err) 199 return err; 200 201 dw_wdt_ping(&dw_wdt->wdd); 202 203 return 0; 204} 205#endif /* CONFIG_PM_SLEEP */ 206 207static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume); 208 209static int dw_wdt_drv_probe(struct platform_device *pdev) 210{ 211 struct device *dev = &pdev->dev; 212 struct watchdog_device *wdd; 213 struct dw_wdt *dw_wdt; 214 struct resource *mem; 215 int ret; 216 217 dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL); 218 if (!dw_wdt) 219 return -ENOMEM; 220 221 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 222 dw_wdt->regs = devm_ioremap_resource(dev, mem); 223 if (IS_ERR(dw_wdt->regs)) 224 return PTR_ERR(dw_wdt->regs); 225 226 dw_wdt->clk = devm_clk_get(dev, NULL); 227 if (IS_ERR(dw_wdt->clk)) 228 return PTR_ERR(dw_wdt->clk); 229 230 ret = clk_prepare_enable(dw_wdt->clk); 231 if (ret) 232 return ret; 233 234 wdd = &dw_wdt->wdd; 235 wdd->info = &dw_wdt_ident; 236 wdd->ops = &dw_wdt_ops; 237 wdd->min_timeout = 1; 238 wdd->max_hw_heartbeat_ms = 239 dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000; 240 wdd->parent = dev; 241 242 watchdog_set_drvdata(wdd, dw_wdt); 243 watchdog_set_nowayout(wdd, nowayout); 244 watchdog_init_timeout(wdd, 0, dev); 245 246 /* 247 * If the watchdog is already running, use its already configured 248 * timeout. Otherwise use the default or the value provided through 249 * devicetree. 250 */ 251 if (dw_wdt_is_enabled(dw_wdt)) { 252 wdd->timeout = dw_wdt_get_top(dw_wdt); 253 set_bit(WDOG_HW_RUNNING, &wdd->status); 254 } else { 255 wdd->timeout = DW_WDT_DEFAULT_SECONDS; 256 watchdog_init_timeout(wdd, 0, dev); 257 } 258 259 platform_set_drvdata(pdev, dw_wdt); 260 261 ret = watchdog_register_device(wdd); 262 if (ret) 263 goto out_disable_clk; 264 265 dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle; 266 dw_wdt->restart_handler.priority = 128; 267 ret = register_restart_handler(&dw_wdt->restart_handler); 268 if (ret) 269 pr_warn("cannot register restart handler\n"); 270 271 return 0; 272 273out_disable_clk: 274 clk_disable_unprepare(dw_wdt->clk); 275 return ret; 276} 277 278static int dw_wdt_drv_remove(struct platform_device *pdev) 279{ 280 struct dw_wdt *dw_wdt = platform_get_drvdata(pdev); 281 282 unregister_restart_handler(&dw_wdt->restart_handler); 283 watchdog_unregister_device(&dw_wdt->wdd); 284 clk_disable_unprepare(dw_wdt->clk); 285 286 return 0; 287} 288 289#ifdef CONFIG_OF 290static const struct of_device_id dw_wdt_of_match[] = { 291 { .compatible = "snps,dw-wdt", }, 292 { /* sentinel */ } 293}; 294MODULE_DEVICE_TABLE(of, dw_wdt_of_match); 295#endif 296 297static struct platform_driver dw_wdt_driver = { 298 .probe = dw_wdt_drv_probe, 299 .remove = dw_wdt_drv_remove, 300 .driver = { 301 .name = "dw_wdt", 302 .of_match_table = of_match_ptr(dw_wdt_of_match), 303 .pm = &dw_wdt_pm_ops, 304 }, 305}; 306 307module_platform_driver(dw_wdt_driver); 308 309MODULE_AUTHOR("Jamie Iles"); 310MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver"); 311MODULE_LICENSE("GPL");