asm9260_wdt.c (8636B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Watchdog driver for Alphascale ASM9260. 4 * 5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de> 6 */ 7 8#include <linux/bitops.h> 9#include <linux/clk.h> 10#include <linux/delay.h> 11#include <linux/interrupt.h> 12#include <linux/io.h> 13#include <linux/module.h> 14#include <linux/of.h> 15#include <linux/platform_device.h> 16#include <linux/reset.h> 17#include <linux/watchdog.h> 18 19#define CLOCK_FREQ 1000000 20 21/* Watchdog Mode register */ 22#define HW_WDMOD 0x00 23/* Wake interrupt. Set by HW, can't be cleared. */ 24#define BM_MOD_WDINT BIT(3) 25/* This bit set if timeout reached. Cleared by SW. */ 26#define BM_MOD_WDTOF BIT(2) 27/* HW Reset on timeout */ 28#define BM_MOD_WDRESET BIT(1) 29/* WD enable */ 30#define BM_MOD_WDEN BIT(0) 31 32/* 33 * Watchdog Timer Constant register 34 * Minimal value is 0xff, the meaning of this value 35 * depends on used clock: T = WDCLK * (0xff + 1) * 4 36 */ 37#define HW_WDTC 0x04 38#define BM_WDTC_MAX(freq) (0x7fffffff / (freq)) 39 40/* Watchdog Feed register */ 41#define HW_WDFEED 0x08 42 43/* Watchdog Timer Value register */ 44#define HW_WDTV 0x0c 45 46#define ASM9260_WDT_DEFAULT_TIMEOUT 30 47 48enum asm9260_wdt_mode { 49 HW_RESET, 50 SW_RESET, 51 DEBUG, 52}; 53 54struct asm9260_wdt_priv { 55 struct device *dev; 56 struct watchdog_device wdd; 57 struct clk *clk; 58 struct clk *clk_ahb; 59 struct reset_control *rst; 60 61 void __iomem *iobase; 62 int irq; 63 unsigned long wdt_freq; 64 enum asm9260_wdt_mode mode; 65}; 66 67static int asm9260_wdt_feed(struct watchdog_device *wdd) 68{ 69 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 70 71 iowrite32(0xaa, priv->iobase + HW_WDFEED); 72 iowrite32(0x55, priv->iobase + HW_WDFEED); 73 74 return 0; 75} 76 77static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd) 78{ 79 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 80 u32 counter; 81 82 counter = ioread32(priv->iobase + HW_WDTV); 83 84 return counter / priv->wdt_freq; 85} 86 87static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd) 88{ 89 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 90 u32 counter; 91 92 counter = wdd->timeout * priv->wdt_freq; 93 94 iowrite32(counter, priv->iobase + HW_WDTC); 95 96 return 0; 97} 98 99static int asm9260_wdt_enable(struct watchdog_device *wdd) 100{ 101 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 102 u32 mode = 0; 103 104 if (priv->mode == HW_RESET) 105 mode = BM_MOD_WDRESET; 106 107 iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD); 108 109 asm9260_wdt_updatetimeout(wdd); 110 111 asm9260_wdt_feed(wdd); 112 113 return 0; 114} 115 116static int asm9260_wdt_disable(struct watchdog_device *wdd) 117{ 118 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 119 120 /* The only way to disable WD is to reset it. */ 121 reset_control_assert(priv->rst); 122 reset_control_deassert(priv->rst); 123 124 return 0; 125} 126 127static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) 128{ 129 wdd->timeout = to; 130 asm9260_wdt_updatetimeout(wdd); 131 132 return 0; 133} 134 135static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv) 136{ 137 /* init WD if it was not started */ 138 139 iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD); 140 141 iowrite32(0xff, priv->iobase + HW_WDTC); 142 /* first pass correct sequence */ 143 asm9260_wdt_feed(&priv->wdd); 144 /* 145 * Then write wrong pattern to the feed to trigger reset 146 * ASAP. 147 */ 148 iowrite32(0xff, priv->iobase + HW_WDFEED); 149 150 mdelay(1000); 151} 152 153static irqreturn_t asm9260_wdt_irq(int irq, void *devid) 154{ 155 struct asm9260_wdt_priv *priv = devid; 156 u32 stat; 157 158 stat = ioread32(priv->iobase + HW_WDMOD); 159 if (!(stat & BM_MOD_WDINT)) 160 return IRQ_NONE; 161 162 if (priv->mode == DEBUG) { 163 dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n"); 164 } else { 165 dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n"); 166 asm9260_wdt_sys_reset(priv); 167 } 168 169 return IRQ_HANDLED; 170} 171 172static int asm9260_restart(struct watchdog_device *wdd, unsigned long action, 173 void *data) 174{ 175 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 176 177 asm9260_wdt_sys_reset(priv); 178 179 return 0; 180} 181 182static const struct watchdog_info asm9260_wdt_ident = { 183 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING 184 | WDIOF_MAGICCLOSE, 185 .identity = "Alphascale asm9260 Watchdog", 186}; 187 188static const struct watchdog_ops asm9260_wdt_ops = { 189 .owner = THIS_MODULE, 190 .start = asm9260_wdt_enable, 191 .stop = asm9260_wdt_disable, 192 .get_timeleft = asm9260_wdt_gettimeleft, 193 .ping = asm9260_wdt_feed, 194 .set_timeout = asm9260_wdt_settimeout, 195 .restart = asm9260_restart, 196}; 197 198static void asm9260_clk_disable_unprepare(void *data) 199{ 200 clk_disable_unprepare(data); 201} 202 203static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv) 204{ 205 int err; 206 unsigned long clk; 207 208 priv->clk = devm_clk_get(priv->dev, "mod"); 209 if (IS_ERR(priv->clk)) { 210 dev_err(priv->dev, "Failed to get \"mod\" clk\n"); 211 return PTR_ERR(priv->clk); 212 } 213 214 /* configure AHB clock */ 215 priv->clk_ahb = devm_clk_get(priv->dev, "ahb"); 216 if (IS_ERR(priv->clk_ahb)) { 217 dev_err(priv->dev, "Failed to get \"ahb\" clk\n"); 218 return PTR_ERR(priv->clk_ahb); 219 } 220 221 err = clk_prepare_enable(priv->clk_ahb); 222 if (err) { 223 dev_err(priv->dev, "Failed to enable ahb_clk!\n"); 224 return err; 225 } 226 err = devm_add_action_or_reset(priv->dev, 227 asm9260_clk_disable_unprepare, 228 priv->clk_ahb); 229 if (err) 230 return err; 231 232 err = clk_set_rate(priv->clk, CLOCK_FREQ); 233 if (err) { 234 dev_err(priv->dev, "Failed to set rate!\n"); 235 return err; 236 } 237 238 err = clk_prepare_enable(priv->clk); 239 if (err) { 240 dev_err(priv->dev, "Failed to enable clk!\n"); 241 return err; 242 } 243 err = devm_add_action_or_reset(priv->dev, 244 asm9260_clk_disable_unprepare, 245 priv->clk); 246 if (err) 247 return err; 248 249 /* wdt has internal divider */ 250 clk = clk_get_rate(priv->clk); 251 if (!clk) { 252 dev_err(priv->dev, "Failed, clk is 0!\n"); 253 return -EINVAL; 254 } 255 256 priv->wdt_freq = clk / 2; 257 258 return 0; 259} 260 261static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv) 262{ 263 const char *tmp; 264 int ret; 265 266 /* default mode */ 267 priv->mode = HW_RESET; 268 269 ret = of_property_read_string(priv->dev->of_node, 270 "alphascale,mode", &tmp); 271 if (ret < 0) 272 return; 273 274 if (!strcmp(tmp, "hw")) 275 priv->mode = HW_RESET; 276 else if (!strcmp(tmp, "sw")) 277 priv->mode = SW_RESET; 278 else if (!strcmp(tmp, "debug")) 279 priv->mode = DEBUG; 280 else 281 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.", 282 tmp); 283} 284 285static int asm9260_wdt_probe(struct platform_device *pdev) 286{ 287 struct device *dev = &pdev->dev; 288 struct asm9260_wdt_priv *priv; 289 struct watchdog_device *wdd; 290 int ret; 291 static const char * const mode_name[] = { "hw", "sw", "debug", }; 292 293 priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL); 294 if (!priv) 295 return -ENOMEM; 296 297 priv->dev = dev; 298 299 priv->iobase = devm_platform_ioremap_resource(pdev, 0); 300 if (IS_ERR(priv->iobase)) 301 return PTR_ERR(priv->iobase); 302 303 priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst"); 304 if (IS_ERR(priv->rst)) 305 return PTR_ERR(priv->rst); 306 307 ret = asm9260_wdt_get_dt_clks(priv); 308 if (ret) 309 return ret; 310 311 wdd = &priv->wdd; 312 wdd->info = &asm9260_wdt_ident; 313 wdd->ops = &asm9260_wdt_ops; 314 wdd->min_timeout = 1; 315 wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq); 316 wdd->parent = dev; 317 318 watchdog_set_drvdata(wdd, priv); 319 320 /* 321 * If 'timeout-sec' unspecified in devicetree, assume a 30 second 322 * default, unless the max timeout is less than 30 seconds, then use 323 * the max instead. 324 */ 325 wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT; 326 watchdog_init_timeout(wdd, 0, dev); 327 328 asm9260_wdt_get_dt_mode(priv); 329 330 if (priv->mode != HW_RESET) 331 priv->irq = platform_get_irq(pdev, 0); 332 333 if (priv->irq > 0) { 334 /* 335 * Not all supported platforms specify an interrupt for the 336 * watchdog, so let's make it optional. 337 */ 338 ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0, 339 pdev->name, priv); 340 if (ret < 0) 341 dev_warn(dev, "failed to request IRQ\n"); 342 } 343 344 watchdog_set_restart_priority(wdd, 128); 345 346 watchdog_stop_on_reboot(wdd); 347 watchdog_stop_on_unregister(wdd); 348 ret = devm_watchdog_register_device(dev, wdd); 349 if (ret) 350 return ret; 351 352 platform_set_drvdata(pdev, priv); 353 354 dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n", 355 wdd->timeout, mode_name[priv->mode]); 356 return 0; 357} 358 359static const struct of_device_id asm9260_wdt_of_match[] = { 360 { .compatible = "alphascale,asm9260-wdt"}, 361 {}, 362}; 363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); 364 365static struct platform_driver asm9260_wdt_driver = { 366 .driver = { 367 .name = "asm9260-wdt", 368 .of_match_table = asm9260_wdt_of_match, 369 }, 370 .probe = asm9260_wdt_probe, 371}; 372module_platform_driver(asm9260_wdt_driver); 373 374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver"); 375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>"); 376MODULE_LICENSE("GPL");