1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2004 Simtec Electronics 4 * Ben Dooks <ben@simtec.co.uk> 5 * 6 * S3C2410 Watchdog Timer Support 7 * 8 * Based on, softdog.c by Alan Cox, 9 * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk> 10 */ 11 12#include <linux/module.h> 13#include <linux/moduleparam.h> 14#include <linux/types.h> 15#include <linux/timer.h> 16#include <linux/watchdog.h> 17#include <linux/platform_device.h> 18#include <linux/interrupt.h> 19#include <linux/clk.h> 20#include <linux/uaccess.h> 21#include <linux/io.h> 22#include <linux/cpufreq.h> 23#include <linux/slab.h> 24#include <linux/err.h> 25#include <linux/of.h> 26#include <linux/mfd/syscon.h> 27#include <linux/regmap.h> 28#include <linux/delay.h> 29 30#define S3C2410_WTCON 0x00 31#define S3C2410_WTDAT 0x04 32#define S3C2410_WTCNT 0x08 33#define S3C2410_WTCLRINT 0x0c 34 35#define S3C2410_WTCNT_MAXCNT 0xffff 36 37#define S3C2410_WTCON_RSTEN (1 << 0) 38#define S3C2410_WTCON_INTEN (1 << 2) 39#define S3C2410_WTCON_ENABLE (1 << 5) 40 41#define S3C2410_WTCON_DIV16 (0 << 3) 42#define S3C2410_WTCON_DIV32 (1 << 3) 43#define S3C2410_WTCON_DIV64 (2 << 3) 44#define S3C2410_WTCON_DIV128 (3 << 3) 45 46#define S3C2410_WTCON_MAXDIV 0x80 47 48#define S3C2410_WTCON_PRESCALE(x) ((x) << 8) 49#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8) 50#define S3C2410_WTCON_PRESCALE_MAX 0xff 51 52#define S3C2410_WATCHDOG_ATBOOT (0) 53#define S3C2410_WATCHDOG_DEFAULT_TIME (15) 54 55#define EXYNOS5_RST_STAT_REG_OFFSET 0x0404 56#define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408 57#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c 58#define EXYNOS850_CLUSTER0_NONCPU_OUT 0x1220 59#define EXYNOS850_CLUSTER0_NONCPU_INT_EN 0x1244 60#define EXYNOS850_CLUSTER1_NONCPU_OUT 0x1620 61#define EXYNOS850_CLUSTER1_NONCPU_INT_EN 0x1644 62#define EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT 0x1520 63#define EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN 0x1544 64 65#define EXYNOS850_CLUSTER0_WDTRESET_BIT 24 66#define EXYNOS850_CLUSTER1_WDTRESET_BIT 23 67#define EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT 25 68#define EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT 24 69 70/** 71 * DOC: Quirk flags for different Samsung watchdog IP-cores 72 * 73 * This driver supports multiple Samsung SoCs, each of which might have 74 * different set of registers and features supported. As watchdog block 75 * sometimes requires modifying PMU registers for proper functioning, register 76 * differences in both watchdog and PMU IP-cores should be accounted for. Quirk 77 * flags described below serve the purpose of telling the driver about mentioned 78 * SoC traits, and can be specified in driver data for each particular supported 79 * device. 80 * 81 * %QUIRK_HAS_WTCLRINT_REG: Watchdog block has WTCLRINT register. It's used to 82 * clear the interrupt once the interrupt service routine is complete. It's 83 * write-only, writing any values to this register clears the interrupt, but 84 * reading is not permitted. 85 * 86 * %QUIRK_HAS_PMU_MASK_RESET: PMU block has the register for disabling/enabling 87 * WDT reset request. On old SoCs it's usually called MASK_WDT_RESET_REQUEST, 88 * new SoCs have CLUSTERx_NONCPU_INT_EN register, which 'mask_bit' value is 89 * inverted compared to the former one. 90 * 91 * %QUIRK_HAS_PMU_RST_STAT: PMU block has RST_STAT (reset status) register, 92 * which contains bits indicating the reason for most recent CPU reset. If 93 * present, driver will use this register to check if previous reboot was due to 94 * watchdog timer reset. 95 * 96 * %QUIRK_HAS_PMU_AUTO_DISABLE: PMU block has AUTOMATIC_WDT_RESET_DISABLE 97 * register. If 'mask_bit' bit is set, PMU will disable WDT reset when 98 * corresponding processor is in reset state. 99 * 100 * %QUIRK_HAS_PMU_CNT_EN: PMU block has some register (e.g. CLUSTERx_NONCPU_OUT) 101 * with "watchdog counter enable" bit. That bit should be set to make watchdog 102 * counter running. 103 */ 104#define QUIRK_HAS_WTCLRINT_REG (1 << 0) 105#define QUIRK_HAS_PMU_MASK_RESET (1 << 1) 106#define QUIRK_HAS_PMU_RST_STAT (1 << 2) 107#define QUIRK_HAS_PMU_AUTO_DISABLE (1 << 3) 108#define QUIRK_HAS_PMU_CNT_EN (1 << 4) 109 110/* These quirks require that we have a PMU register map */ 111#define QUIRKS_HAVE_PMUREG \ 112 (QUIRK_HAS_PMU_MASK_RESET | QUIRK_HAS_PMU_RST_STAT | \ 113 QUIRK_HAS_PMU_AUTO_DISABLE | QUIRK_HAS_PMU_CNT_EN) 114 115static bool nowayout = WATCHDOG_NOWAYOUT; 116static int tmr_margin; 117static int tmr_atboot = S3C2410_WATCHDOG_ATBOOT; 118static int soft_noboot; 119 120module_param(tmr_margin, int, 0); 121module_param(tmr_atboot, int, 0); 122module_param(nowayout, bool, 0); 123module_param(soft_noboot, int, 0); 124 125MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" 126 __MODULE_STRING(S3C2410_WATCHDOG_DEFAULT_TIME) ")"); 127MODULE_PARM_DESC(tmr_atboot, 128 "Watchdog is started at boot time if set to 1, default=" 129 __MODULE_STRING(S3C2410_WATCHDOG_ATBOOT)); 130MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 131 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 132MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default 0)"); 133 134/** 135 * struct s3c2410_wdt_variant - Per-variant config data 136 * 137 * @disable_reg: Offset in pmureg for the register that disables the watchdog 138 * timer reset functionality. 139 * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog 140 * timer reset functionality. 141 * @mask_reset_inv: If set, mask_reset_reg value will have inverted meaning. 142 * @mask_bit: Bit number for the watchdog timer in the disable register and the 143 * mask reset register. 144 * @rst_stat_reg: Offset in pmureg for the register that has the reset status. 145 * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog 146 * reset. 147 * @cnt_en_reg: Offset in pmureg for the register that enables WDT counter. 148 * @cnt_en_bit: Bit number for "watchdog counter enable" in cnt_en register. 149 * @quirks: A bitfield of quirks. 150 */ 151 152struct s3c2410_wdt_variant { 153 int disable_reg; 154 int mask_reset_reg; 155 bool mask_reset_inv; 156 int mask_bit; 157 int rst_stat_reg; 158 int rst_stat_bit; 159 int cnt_en_reg; 160 int cnt_en_bit; 161 u32 quirks; 162}; 163 164struct s3c2410_wdt { 165 struct device *dev; 166 struct clk *bus_clk; /* for register interface (PCLK) */ 167 struct clk *src_clk; /* for WDT counter */ 168 void __iomem *reg_base; 169 unsigned int count; 170 spinlock_t lock; 171 unsigned long wtcon_save; 172 unsigned long wtdat_save; 173 struct watchdog_device wdt_device; 174 struct notifier_block freq_transition; 175 const struct s3c2410_wdt_variant *drv_data; 176 struct regmap *pmureg; 177}; 178 179static const struct s3c2410_wdt_variant drv_data_s3c2410 = { 180 .quirks = 0 181}; 182 183#ifdef CONFIG_OF 184static const struct s3c2410_wdt_variant drv_data_s3c6410 = { 185 .quirks = QUIRK_HAS_WTCLRINT_REG, 186}; 187 188static const struct s3c2410_wdt_variant drv_data_exynos5250 = { 189 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, 190 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, 191 .mask_bit = 20, 192 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 193 .rst_stat_bit = 20, 194 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ 195 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, 196}; 197 198static const struct s3c2410_wdt_variant drv_data_exynos5420 = { 199 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, 200 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, 201 .mask_bit = 0, 202 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 203 .rst_stat_bit = 9, 204 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ 205 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, 206}; 207 208static const struct s3c2410_wdt_variant drv_data_exynos7 = { 209 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, 210 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, 211 .mask_bit = 23, 212 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 213 .rst_stat_bit = 23, /* A57 WDTRESET */ 214 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ 215 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, 216}; 217 218static const struct s3c2410_wdt_variant drv_data_exynos850_cl0 = { 219 .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN, 220 .mask_bit = 2, 221 .mask_reset_inv = true, 222 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 223 .rst_stat_bit = EXYNOS850_CLUSTER0_WDTRESET_BIT, 224 .cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT, 225 .cnt_en_bit = 7, 226 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ 227 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, 228}; 229 230static const struct s3c2410_wdt_variant drv_data_exynos850_cl1 = { 231 .mask_reset_reg = EXYNOS850_CLUSTER1_NONCPU_INT_EN, 232 .mask_bit = 2, 233 .mask_reset_inv = true, 234 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 235 .rst_stat_bit = EXYNOS850_CLUSTER1_WDTRESET_BIT, 236 .cnt_en_reg = EXYNOS850_CLUSTER1_NONCPU_OUT, 237 .cnt_en_bit = 7, 238 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ 239 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, 240}; 241 242static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl0 = { 243 .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN, 244 .mask_bit = 2, 245 .mask_reset_inv = true, 246 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 247 .rst_stat_bit = EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT, 248 .cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT, 249 .cnt_en_bit = 7, 250 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | 251 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, 252}; 253 254static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl1 = { 255 .mask_reset_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN, 256 .mask_bit = 2, 257 .mask_reset_inv = true, 258 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 259 .rst_stat_bit = EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT, 260 .cnt_en_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT, 261 .cnt_en_bit = 7, 262 .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | 263 QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, 264}; 265 266static const struct of_device_id s3c2410_wdt_match[] = { 267 { .compatible = "samsung,s3c2410-wdt", 268 .data = &drv_data_s3c2410 }, 269 { .compatible = "samsung,s3c6410-wdt", 270 .data = &drv_data_s3c6410 }, 271 { .compatible = "samsung,exynos5250-wdt", 272 .data = &drv_data_exynos5250 }, 273 { .compatible = "samsung,exynos5420-wdt", 274 .data = &drv_data_exynos5420 }, 275 { .compatible = "samsung,exynos7-wdt", 276 .data = &drv_data_exynos7 }, 277 { .compatible = "samsung,exynos850-wdt", 278 .data = &drv_data_exynos850_cl0 }, 279 { .compatible = "samsung,exynosautov9-wdt", 280 .data = &drv_data_exynosautov9_cl0 }, 281 {}, 282}; 283MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); 284#endif 285 286static const struct platform_device_id s3c2410_wdt_ids[] = { 287 { 288 .name = "s3c2410-wdt", 289 .driver_data = (unsigned long)&drv_data_s3c2410, 290 }, 291 {} 292}; 293MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids); 294 295/* functions */ 296 297static inline unsigned long s3c2410wdt_get_freq(struct s3c2410_wdt *wdt) 298{ 299 return clk_get_rate(wdt->src_clk ? wdt->src_clk : wdt->bus_clk); 300} 301 302static inline unsigned int s3c2410wdt_max_timeout(struct s3c2410_wdt *wdt) 303{ 304 const unsigned long freq = s3c2410wdt_get_freq(wdt); 305 306 return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1) 307 / S3C2410_WTCON_MAXDIV); 308} 309 310static int s3c2410wdt_disable_wdt_reset(struct s3c2410_wdt *wdt, bool mask) 311{ 312 const u32 mask_val = BIT(wdt->drv_data->mask_bit); 313 const u32 val = mask ? mask_val : 0; 314 int ret; 315 316 ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->disable_reg, 317 mask_val, val); 318 if (ret < 0) 319 dev_err(wdt->dev, "failed to update reg(%d)\n", ret); 320 321 return ret; 322} 323 324static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask) 325{ 326 const u32 mask_val = BIT(wdt->drv_data->mask_bit); 327 const bool val_inv = wdt->drv_data->mask_reset_inv; 328 const u32 val = (mask ^ val_inv) ? mask_val : 0; 329 int ret; 330 331 ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->mask_reset_reg, 332 mask_val, val); 333 if (ret < 0) 334 dev_err(wdt->dev, "failed to update reg(%d)\n", ret); 335 336 return ret; 337} 338 339static int s3c2410wdt_enable_counter(struct s3c2410_wdt *wdt, bool en) 340{ 341 const u32 mask_val = BIT(wdt->drv_data->cnt_en_bit); 342 const u32 val = en ? mask_val : 0; 343 int ret; 344 345 ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->cnt_en_reg, 346 mask_val, val); 347 if (ret < 0) 348 dev_err(wdt->dev, "failed to update reg(%d)\n", ret); 349 350 return ret; 351} 352 353static int s3c2410wdt_enable(struct s3c2410_wdt *wdt, bool en) 354{ 355 int ret; 356 357 if (wdt->drv_data->quirks & QUIRK_HAS_PMU_AUTO_DISABLE) { 358 ret = s3c2410wdt_disable_wdt_reset(wdt, !en); 359 if (ret < 0) 360 return ret; 361 } 362 363 if (wdt->drv_data->quirks & QUIRK_HAS_PMU_MASK_RESET) { 364 ret = s3c2410wdt_mask_wdt_reset(wdt, !en); 365 if (ret < 0) 366 return ret; 367 } 368 369 if (wdt->drv_data->quirks & QUIRK_HAS_PMU_CNT_EN) { 370 ret = s3c2410wdt_enable_counter(wdt, en); 371 if (ret < 0) 372 return ret; 373 } 374 375 return 0; 376} 377 378static int s3c2410wdt_keepalive(struct watchdog_device *wdd) 379{ 380 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 381 unsigned long flags; 382 383 spin_lock_irqsave(&wdt->lock, flags); 384 writel(wdt->count, wdt->reg_base + S3C2410_WTCNT); 385 spin_unlock_irqrestore(&wdt->lock, flags); 386 387 return 0; 388} 389 390static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt) 391{ 392 unsigned long wtcon; 393 394 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 395 wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); 396 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 397} 398 399static int s3c2410wdt_stop(struct watchdog_device *wdd) 400{ 401 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 402 unsigned long flags; 403 404 spin_lock_irqsave(&wdt->lock, flags); 405 __s3c2410wdt_stop(wdt); 406 spin_unlock_irqrestore(&wdt->lock, flags); 407 408 return 0; 409} 410 411static int s3c2410wdt_start(struct watchdog_device *wdd) 412{ 413 unsigned long wtcon; 414 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 415 unsigned long flags; 416 417 spin_lock_irqsave(&wdt->lock, flags); 418 419 __s3c2410wdt_stop(wdt); 420 421 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 422 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 423 424 if (soft_noboot) { 425 wtcon |= S3C2410_WTCON_INTEN; 426 wtcon &= ~S3C2410_WTCON_RSTEN; 427 } else { 428 wtcon &= ~S3C2410_WTCON_INTEN; 429 wtcon |= S3C2410_WTCON_RSTEN; 430 } 431 432 dev_dbg(wdt->dev, "Starting watchdog: count=0x%08x, wtcon=%08lx\n", 433 wdt->count, wtcon); 434 435 writel(wdt->count, wdt->reg_base + S3C2410_WTDAT); 436 writel(wdt->count, wdt->reg_base + S3C2410_WTCNT); 437 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 438 spin_unlock_irqrestore(&wdt->lock, flags); 439 440 return 0; 441} 442 443static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, 444 unsigned int timeout) 445{ 446 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 447 unsigned long freq = s3c2410wdt_get_freq(wdt); 448 unsigned int count; 449 unsigned int divisor = 1; 450 unsigned long wtcon; 451 452 if (timeout < 1) 453 return -EINVAL; 454 455 freq = DIV_ROUND_UP(freq, 128); 456 count = timeout * freq; 457 458 dev_dbg(wdt->dev, "Heartbeat: count=%d, timeout=%d, freq=%lu\n", 459 count, timeout, freq); 460 461 /* if the count is bigger than the watchdog register, 462 then work out what we need to do (and if) we can 463 actually make this value 464 */ 465 466 if (count >= 0x10000) { 467 divisor = DIV_ROUND_UP(count, 0xffff); 468 469 if (divisor > 0x100) { 470 dev_err(wdt->dev, "timeout %d too big\n", timeout); 471 return -EINVAL; 472 } 473 } 474 475 dev_dbg(wdt->dev, "Heartbeat: timeout=%d, divisor=%d, count=%d (%08x)\n", 476 timeout, divisor, count, DIV_ROUND_UP(count, divisor)); 477 478 count = DIV_ROUND_UP(count, divisor); 479 wdt->count = count; 480 481 /* update the pre-scaler */ 482 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 483 wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; 484 wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); 485 486 writel(count, wdt->reg_base + S3C2410_WTDAT); 487 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 488 489 wdd->timeout = (count * divisor) / freq; 490 491 return 0; 492} 493 494static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action, 495 void *data) 496{ 497 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 498 void __iomem *wdt_base = wdt->reg_base; 499 500 /* disable watchdog, to be safe */ 501 writel(0, wdt_base + S3C2410_WTCON); 502 503 /* put initial values into count and data */ 504 writel(0x80, wdt_base + S3C2410_WTCNT); 505 writel(0x80, wdt_base + S3C2410_WTDAT); 506 507 /* set the watchdog to go and reset... */ 508 writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 | 509 S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20), 510 wdt_base + S3C2410_WTCON); 511 512 /* wait for reset to assert... */ 513 mdelay(500); 514 515 return 0; 516} 517 518#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) 519 520static const struct watchdog_info s3c2410_wdt_ident = { 521 .options = OPTIONS, 522 .firmware_version = 0, 523 .identity = "S3C2410 Watchdog", 524}; 525 526static const struct watchdog_ops s3c2410wdt_ops = { 527 .owner = THIS_MODULE, 528 .start = s3c2410wdt_start, 529 .stop = s3c2410wdt_stop, 530 .ping = s3c2410wdt_keepalive, 531 .set_timeout = s3c2410wdt_set_heartbeat, 532 .restart = s3c2410wdt_restart, 533}; 534 535static const struct watchdog_device s3c2410_wdd = { 536 .info = &s3c2410_wdt_ident, 537 .ops = &s3c2410wdt_ops, 538 .timeout = S3C2410_WATCHDOG_DEFAULT_TIME, 539}; 540 541/* interrupt handler code */ 542 543static irqreturn_t s3c2410wdt_irq(int irqno, void *param) 544{ 545 struct s3c2410_wdt *wdt = platform_get_drvdata(param); 546 547 dev_info(wdt->dev, "watchdog timer expired (irq)\n"); 548 549 s3c2410wdt_keepalive(&wdt->wdt_device); 550 551 if (wdt->drv_data->quirks & QUIRK_HAS_WTCLRINT_REG) 552 writel(0x1, wdt->reg_base + S3C2410_WTCLRINT); 553 554 return IRQ_HANDLED; 555} 556 557static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt) 558{ 559 unsigned int rst_stat; 560 int ret; 561 562 if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_RST_STAT)) 563 return 0; 564 565 ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat); 566 if (ret) 567 dev_warn(wdt->dev, "Couldn't get RST_STAT register\n"); 568 else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit)) 569 return WDIOF_CARDRESET; 570 571 return 0; 572} 573 574static inline int 575s3c2410_get_wdt_drv_data(struct platform_device *pdev, struct s3c2410_wdt *wdt) 576{ 577 const struct s3c2410_wdt_variant *variant; 578 struct device *dev = &pdev->dev; 579 580 variant = of_device_get_match_data(dev); 581 if (!variant) { 582 /* Device matched by platform_device_id */ 583 variant = (struct s3c2410_wdt_variant *) 584 platform_get_device_id(pdev)->driver_data; 585 } 586 587#ifdef CONFIG_OF 588 /* Choose Exynos850/ExynosAutov9 driver data w.r.t. cluster index */ 589 if (variant == &drv_data_exynos850_cl0 || 590 variant == &drv_data_exynosautov9_cl0) { 591 u32 index; 592 int err; 593 594 err = of_property_read_u32(dev->of_node, 595 "samsung,cluster-index", &index); 596 if (err) 597 return dev_err_probe(dev, -EINVAL, "failed to get cluster index\n"); 598 599 switch (index) { 600 case 0: 601 break; 602 case 1: 603 variant = (variant == &drv_data_exynos850_cl0) ? 604 &drv_data_exynos850_cl1 : 605 &drv_data_exynosautov9_cl1; 606 break; 607 default: 608 return dev_err_probe(dev, -EINVAL, "wrong cluster index: %u\n", index); 609 } 610 } 611#endif 612 613 wdt->drv_data = variant; 614 return 0; 615} 616 617static void s3c2410wdt_wdt_disable_action(void *data) 618{ 619 s3c2410wdt_enable(data, false); 620} 621 622static int s3c2410wdt_probe(struct platform_device *pdev) 623{ 624 struct device *dev = &pdev->dev; 625 struct s3c2410_wdt *wdt; 626 unsigned int wtcon; 627 int wdt_irq; 628 int ret; 629 630 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 631 if (!wdt) 632 return -ENOMEM; 633 634 wdt->dev = dev; 635 spin_lock_init(&wdt->lock); 636 wdt->wdt_device = s3c2410_wdd; 637 638 ret = s3c2410_get_wdt_drv_data(pdev, wdt); 639 if (ret) 640 return ret; 641 642 if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) { 643 wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, 644 "samsung,syscon-phandle"); 645 if (IS_ERR(wdt->pmureg)) 646 return dev_err_probe(dev, PTR_ERR(wdt->pmureg), 647 "syscon regmap lookup failed.\n"); 648 } 649 650 wdt_irq = platform_get_irq(pdev, 0); 651 if (wdt_irq < 0) 652 return wdt_irq; 653 654 /* get the memory region for the watchdog timer */ 655 wdt->reg_base = devm_platform_ioremap_resource(pdev, 0); 656 if (IS_ERR(wdt->reg_base)) 657 return PTR_ERR(wdt->reg_base); 658 659 wdt->bus_clk = devm_clk_get_enabled(dev, "watchdog"); 660 if (IS_ERR(wdt->bus_clk)) 661 return dev_err_probe(dev, PTR_ERR(wdt->bus_clk), "failed to get bus clock\n"); 662 663 /* 664 * "watchdog_src" clock is optional; if it's not present -- just skip it 665 * and use "watchdog" clock as both bus and source clock. 666 */ 667 wdt->src_clk = devm_clk_get_optional_enabled(dev, "watchdog_src"); 668 if (IS_ERR(wdt->src_clk)) 669 return dev_err_probe(dev, PTR_ERR(wdt->src_clk), "failed to get source clock\n"); 670 671 wdt->wdt_device.min_timeout = 1; 672 wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt); 673 674 watchdog_set_drvdata(&wdt->wdt_device, wdt); 675 676 /* see if we can actually set the requested timer margin, and if 677 * not, try the default value */ 678 679 watchdog_init_timeout(&wdt->wdt_device, tmr_margin, dev); 680 ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, 681 wdt->wdt_device.timeout); 682 if (ret) { 683 ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, 684 S3C2410_WATCHDOG_DEFAULT_TIME); 685 if (ret == 0) 686 dev_warn(dev, "tmr_margin value out of range, default %d used\n", 687 S3C2410_WATCHDOG_DEFAULT_TIME); 688 else 689 return dev_err_probe(dev, ret, "failed to use default timeout\n"); 690 } 691 692 ret = devm_request_irq(dev, wdt_irq, s3c2410wdt_irq, 0, 693 pdev->name, pdev); 694 if (ret != 0) 695 return dev_err_probe(dev, ret, "failed to install irq (%d)\n", ret); 696 697 watchdog_set_nowayout(&wdt->wdt_device, nowayout); 698 watchdog_set_restart_priority(&wdt->wdt_device, 128); 699 700 wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt); 701 wdt->wdt_device.parent = dev; 702 703 /* 704 * If "tmr_atboot" param is non-zero, start the watchdog right now. Also 705 * set WDOG_HW_RUNNING bit, so that watchdog core can kick the watchdog. 706 * 707 * If we're not enabling the watchdog, then ensure it is disabled if it 708 * has been left running from the bootloader or other source. 709 */ 710 if (tmr_atboot) { 711 dev_info(dev, "starting watchdog timer\n"); 712 s3c2410wdt_start(&wdt->wdt_device); 713 set_bit(WDOG_HW_RUNNING, &wdt->wdt_device.status); 714 } else { 715 s3c2410wdt_stop(&wdt->wdt_device); 716 } 717 718 ret = devm_watchdog_register_device(dev, &wdt->wdt_device); 719 if (ret) 720 return ret; 721 722 ret = s3c2410wdt_enable(wdt, true); 723 if (ret < 0) 724 return ret; 725 726 ret = devm_add_action_or_reset(dev, s3c2410wdt_wdt_disable_action, wdt); 727 if (ret) 728 return ret; 729 730 platform_set_drvdata(pdev, wdt); 731 732 /* print out a statement of readiness */ 733 734 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 735 736 dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", 737 (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", 738 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis", 739 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); 740 741 return 0; 742} 743 744static void s3c2410wdt_shutdown(struct platform_device *dev) 745{ 746 struct s3c2410_wdt *wdt = platform_get_drvdata(dev); 747 748 s3c2410wdt_enable(wdt, false); 749 s3c2410wdt_stop(&wdt->wdt_device); 750} 751 752static int s3c2410wdt_suspend(struct device *dev) 753{ 754 int ret; 755 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 756 757 /* Save watchdog state, and turn it off. */ 758 wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); 759 wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); 760 761 ret = s3c2410wdt_enable(wdt, false); 762 if (ret < 0) 763 return ret; 764 765 /* Note that WTCNT doesn't need to be saved. */ 766 s3c2410wdt_stop(&wdt->wdt_device); 767 768 return 0; 769} 770 771static int s3c2410wdt_resume(struct device *dev) 772{ 773 int ret; 774 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 775 776 /* Restore watchdog state. */ 777 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT); 778 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ 779 writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); 780 781 ret = s3c2410wdt_enable(wdt, true); 782 if (ret < 0) 783 return ret; 784 785 dev_info(dev, "watchdog %sabled\n", 786 (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 787 788 return 0; 789} 790 791static DEFINE_SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, 792 s3c2410wdt_suspend, s3c2410wdt_resume); 793 794static struct platform_driver s3c2410wdt_driver = { 795 .probe = s3c2410wdt_probe, 796 .shutdown = s3c2410wdt_shutdown, 797 .id_table = s3c2410_wdt_ids, 798 .driver = { 799 .name = "s3c2410-wdt", 800 .pm = pm_sleep_ptr(&s3c2410wdt_pm_ops), 801 .of_match_table = of_match_ptr(s3c2410_wdt_match), 802 }, 803}; 804 805module_platform_driver(s3c2410wdt_driver); 806 807MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Dimitry Andric <dimitry.andric@tomtom.com>"); 808MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 809MODULE_LICENSE("GPL"); 810