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