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