kernel_samsung_a53x/drivers/watchdog/s3c2410_wdt.c
2024-06-15 16:02:09 -03:00

1776 lines
50 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2004 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2410 Watchdog Timer Support
*
* Based on, softdog.c by Alan Cox,
* (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/rtc.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/soc/samsung/exynos-soc.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include <soc/samsung/exynos-pmu-if.h>
#include <soc/samsung/debug-snapshot.h>
#include "s3c2410_wdt.h"
static bool nowayout = WATCHDOG_NOWAYOUT;
static int tmr_margin;
static int tmr_atboot = S3C2410_WATCHDOG_ATBOOT;
static int soft_noboot;
module_param(tmr_margin, int, 0);
module_param(tmr_atboot, int, 0);
module_param(nowayout, bool, 0);
module_param(soft_noboot, int, 0);
MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
__MODULE_STRING(S3C2410_WATCHDOG_DEFAULT_TIME) ")");
MODULE_PARM_DESC(tmr_atboot,
"Watchdog is started at boot time if set to 1, default="
__MODULE_STRING(S3C2410_WATCHDOG_ATBOOT));
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default 0)");
struct s3c2410_wdt {
struct device *dev;
struct clk *rate_clock;
struct clk *gate_clock;
void __iomem *reg_base;
unsigned int count;
unsigned int shutdown_timeout;
/* lock to serialize watchdog operation */
spinlock_t lock;
unsigned long wtcon_save;
unsigned long wtdat_save;
unsigned long freq;
struct watchdog_device wdt_device;
struct notifier_block freq_transition;
const struct s3c2410_wdt_variant *drv_data;
struct regmap *pmureg;
unsigned int cluster;
int use_multistage_wdt;
unsigned int disable_reg_val;
unsigned int mask_reset_reg_val;
unsigned int noncpu_int_reg_val;
unsigned int noncpu_out_reg_val;
int in_suspend;
int in_panic;
struct task_struct *tsk;
struct thread_info *thr;
struct rtc_time tm;
int last_ping_cpu;
};
/**
* struct s3c2410_wdt_variant - Per-variant config data
*
* @disable_reg: Offset in pmureg for the register that disables the watchdog
* timer reset functionality.
* @mask_reset_reg: Offset in pmureg for the register that masks the watchdog
* timer reset functionality.
* @mask_bit: Bit number for the watchdog timer in the disable register and the
* mask reset register.
* @rst_stat_reg: Offset in pmureg for the register that has the reset status.
* @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog
* reset.
* @quirks: A bitfield of quirks.
*/
struct s3c2410_wdt_variant {
int disable_reg;
int mask_reset_reg;
int mask_bit;
int cnt_en_bit;
int rst_stat_reg;
int rst_stat_bit;
int noncpu_int_en;
int noncpu_out;
u32 quirks;
int (*pmu_reset_func)(struct s3c2410_wdt *, bool);
int (*pmu_count_en_func)(struct s3c2410_wdt *, bool);
int (*auto_disable_func)(struct s3c2410_wdt *, bool);
};
static struct s3c2410_wdt *s3c_wdt[MAX_WATCHDOG_CLUSTER_CNT];
static int s3c2410wdt_multistage_wdt_stop(void);
static int s3c2410wdt_multistage_wdt_start(void);
static int s3c2410wdt_multistage_set_heartbeat(struct s3c2410_wdt *wdt, int ratio);
static void s3c2410wdt_multistage_wdt_keepalive(void);
static int s3c2410wdt_get_multistage_index(void);
static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask);
static int s3c2410wdt_automatic_disable_wdt(struct s3c2410_wdt *wdt, bool mask);
static int s3c2410wdt_noncpu_int_en(struct s3c2410_wdt *wdt, bool mask);
static int s3c2410wdt_noncpu_out(struct s3c2410_wdt *wdt, bool mask);
static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = 0
};
#ifdef CONFIG_OF
static const struct s3c2410_wdt_variant drv_data_s3c6410 = {
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos5250 = {
.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
.mask_bit = 20,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 20,
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
.mask_bit = 0,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 9,
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos7 = {
.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
.mask_bit = 23,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 23, /* A57 WDTRESET */
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos8 = {
.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
.mask_bit = 24,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* A53 WDTRESET */
.pmu_reset_func = s3c2410wdt_mask_wdt_reset,
.auto_disable_func = s3c2410wdt_automatic_disable_wdt,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
/* PMU registers are changed.
* MASK_RESET register was replaced by CLUSTERx_NONCPU_INT_TYPE register.
* DISABLE register was removed and its value was always fixed by 0.
*/
static const struct s3c2410_wdt_variant drv_data_exynos9_v1 = {
.noncpu_int_en = EXYNOS_CLUSTER0_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos9_v2 = {
.noncpu_int_en = EXYNOS_CLUSTER2_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 23, /* CLUSTER2 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos9_v3 = {
.noncpu_int_en = EXYNOS_CLUSTER0_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos9_v4 = {
.noncpu_int_en = EXYNOS_CLUSTER2_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 23, /* CLUSTER2 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos9630_v1 = {
.noncpu_int_en = EXYNOS9630_CLUSTER0_NONCPU_INT_EN,
.noncpu_out = EXYNOS9630_CLUSTER0_NONCPU_OUT,
.mask_bit = 2,
.cnt_en_bit = 7,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos9630_v2 = {
.noncpu_int_en = EXYNOS9630_CLUSTER1_NONCPU_INT_EN,
.noncpu_out = EXYNOS9630_CLUSTER1_NONCPU_OUT,
.mask_bit = 3,
.cnt_en_bit = 7,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 25, /* CLUSTER1 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos2100_v1 = {
.noncpu_int_en = EXYNOS2100_CLUSTER0_NONCPU_INT_EN,
.noncpu_out = EXYNOS2100_CLUSTER0_NONCPU_OUT,
.mask_bit = 2,
.cnt_en_bit = 9,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_exynos2100_v2 = {
.noncpu_int_en = EXYNOS2100_CLUSTER2_NONCPU_INT_EN,
.noncpu_out = EXYNOS2100_CLUSTER2_NONCPU_OUT,
.mask_bit = 3,
.cnt_en_bit = 7,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 25, /* CLUSTER2 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_s5e9925_v1 = {
.noncpu_int_en = S5E9925_CLUSTER0_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_s5e9925_v2 = {
.noncpu_int_en = S5E9925_CLUSTER2_NONCPU_INT_EN,
.mask_bit = 3,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 25, /* CLUSTER2 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_s5e8825_v1 = {
.noncpu_int_en = S5E8825_CLUSTER0_NONCPU_INT_EN,
.mask_bit = 2,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 24, /* CLUSTER0 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct s3c2410_wdt_variant drv_data_s5e8825_v2 = {
.noncpu_int_en = S5E8825_CLUSTER1_NONCPU_INT_EN,
.mask_bit = 3,
.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
.rst_stat_bit = 25, /* CLUSTER1 WDTRESET */
.pmu_reset_func = s3c2410wdt_noncpu_int_en,
.pmu_count_en_func = s3c2410wdt_noncpu_out,
.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT | QUIRK_HAS_WTCLRINT_REG |
QUIRK_HAS_DBGACK_BIT | QUIRK_HAS_WTMINCNT_REG,
};
static const struct of_device_id s3c2410_wdt_match[] = {
{ .compatible = "samsung,s3c2410-wdt",
.data = &drv_data_s3c2410 },
{ .compatible = "samsung,s3c6410-wdt",
.data = &drv_data_s3c6410 },
{ .compatible = "samsung,exynos5250-wdt",
.data = &drv_data_exynos5250 },
{ .compatible = "samsung,exynos5420-wdt",
.data = &drv_data_exynos5420 },
{ .compatible = "samsung,exynos7-wdt",
.data = &drv_data_exynos7 },
{ .compatible = "samsung,exynos8-wdt",
.data = &drv_data_exynos8 },
{ .compatible = "samsung,exynos9-v1-wdt",
.data = &drv_data_exynos9_v1 },
{ .compatible = "samsung,exynos9-v2-wdt",
.data = &drv_data_exynos9_v2 },
{ .compatible = "samsung,exynos9-v3-wdt",
.data = &drv_data_exynos9_v3 },
{ .compatible = "samsung,exynos9-v4-wdt",
.data = &drv_data_exynos9_v4 },
{ .compatible = "samsung,exynos9630-v1-wdt",
.data = &drv_data_exynos9630_v1 },
{ .compatible = "samsung,exynos9630-v2-wdt",
.data = &drv_data_exynos9630_v2 },
{ .compatible = "samsung,exynos2100-v1-wdt",
.data = &drv_data_exynos2100_v1 },
{ .compatible = "samsung,exynos2100-v2-wdt",
.data = &drv_data_exynos2100_v2 },
{ .compatible = "samsung,s5e9925-v1-wdt",
.data = &drv_data_s5e9925_v1 },
{ .compatible = "samsung,s5e9925-v2-wdt",
.data = &drv_data_s5e9925_v2 },
{ .compatible = "samsung,s5e8825-v1-wdt",
.data = &drv_data_s5e8825_v1 },
{ .compatible = "samsung,s5e8825-v2-wdt",
.data = &drv_data_s5e8825_v2 },
{},
};
MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
#endif
static const struct platform_device_id s3c2410_wdt_ids[] = {
{
.name = "s3c2410-wdt",
.driver_data = (unsigned long)&drv_data_s3c2410,
},
{}
};
MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
/* functions */
static inline unsigned int s3c2410wdt_max_timeout(struct clk *clock)
{
unsigned long freq = clk_get_rate(clock);
return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1)
/ S3C2410_WTCON_MAXDIV);
}
static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
{
return container_of(nb, struct s3c2410_wdt, freq_transition);
}
static int s3c2410wdt_noncpu_int_en(struct s3c2410_wdt *wdt, bool mask)
{
int ret;
u32 mask_val = 1 << wdt->drv_data->mask_bit;
u32 val = mask_val, reg_val = 0;
/* No need to do anything if no PMU CONFIG needed */
if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
return 0;
/* If mask value is ture, wdt interrupt disable */
if (mask)
val = 0;
ret = exynos_pmu_update(wdt->drv_data->noncpu_int_en, mask_val, val);
if (ret < 0) {
dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
return ret;
}
ret = exynos_pmu_read(wdt->drv_data->noncpu_int_en, &reg_val);
if (ret < 0) {
dev_err(wdt->dev,
"Couldn't get NONCPU_INT_EN register, ret = (%d)\n", ret);
return ret;
}
wdt->noncpu_int_reg_val = reg_val;
dev_info(wdt->dev,
"NONCPU_INT_EN set %s done, val = %x, mask = %d\n",
val ? "true" : "false", reg_val, mask);
return ret;
}
static int s3c2410wdt_noncpu_out(struct s3c2410_wdt *wdt, bool en)
{
int ret;
u32 cnt_en_val = 1 << wdt->drv_data->cnt_en_bit;
u32 val = cnt_en_val, reg_val = 0;
/* No need to do anything if no PMU CONFIG needed */
if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
return 0;
/* If en value is false, wdt counter disable */
if (!en)
val = 0;
ret = exynos_pmu_update(wdt->drv_data->noncpu_out, cnt_en_val, val);
if (ret < 0) {
dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
return ret;
}
ret = exynos_pmu_read(wdt->drv_data->noncpu_out, &reg_val);
if (ret < 0) {
dev_err(wdt->dev,
"Couldn't get NONCPU_OUT register, ret = (%d)\n", ret);
return ret;
}
wdt->noncpu_out_reg_val = reg_val;
dev_info(wdt->dev,
"NONCPU_OUT set %s done, val = %x, en= %d\n",
val ? "true" : "false", reg_val, en);
return ret;
}
static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask)
{
int ret;
u32 mask_val = 1 << wdt->drv_data->mask_bit;
u32 val = 0, mask_reset_reg_val = 0, disable_reg_val = 0;
/* No need to do anything if no PMU CONFIG needed */
if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
return 0;
if (mask)
val = mask_val;
ret = regmap_update_bits(wdt->pmureg,
wdt->drv_data->mask_reset_reg,
mask_val, val);
if (ret < 0) {
dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
return ret;
}
ret = regmap_read(wdt->pmureg, wdt->drv_data->mask_reset_reg, &mask_reset_reg_val);
if (ret < 0) {
dev_err(wdt->dev, "Couldn't get MASK_WDT_RESET register, ret = (%d)\n", ret);
return ret;
}
ret = regmap_read(wdt->pmureg, wdt->drv_data->disable_reg, &disable_reg_val);
if (ret < 0) {
dev_err(wdt->dev, "Couldn't get DISABLE_WDT register, ret = (%d)\n", ret);
return ret;
}
dev_info(wdt->dev, "DISABLE_WDT reg val: %x, MASK_WDT_RESET reg val: %x\n",
disable_reg_val, mask_reset_reg_val);
wdt->disable_reg_val = disable_reg_val;
wdt->mask_reset_reg_val = mask_reset_reg_val;
dev_info(wdt->dev, "Mask_wdt_reset set %s done, mask = %d\n", mask ? "true" : "false", mask);
return ret;
}
static int s3c2410wdt_automatic_disable_wdt(struct s3c2410_wdt *wdt, bool mask)
{
int ret;
u32 mask_val = 1 << wdt->drv_data->mask_bit;
u32 val = 0, mask_reset_reg_val = 0, disable_reg_val = 0;
/* No need to do anything if no PMU CONFIG needed */
if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
return 0;
if (mask)
val = mask_val;
ret = regmap_update_bits(wdt->pmureg,
wdt->drv_data->disable_reg,
mask_val, val);
if (ret < 0) {
dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
return ret;
}
ret = regmap_read(wdt->pmureg, wdt->drv_data->mask_reset_reg, &mask_reset_reg_val);
if (ret < 0) {
dev_err(wdt->dev, "Couldn't get MASK_WDT_RESET register, ret = (%d)\n", ret);
return ret;
}
ret = regmap_read(wdt->pmureg, wdt->drv_data->disable_reg, &disable_reg_val);
if (ret < 0) {
dev_err(wdt->dev, "Couldn't get DISABLE_WDT register, ret = (%d)\n", ret);
return ret;
}
dev_info(wdt->dev, "DISABLE_WDT reg val: %x, MASK_WDT_RESET reg val: %x\n",
disable_reg_val, mask_reset_reg_val);
wdt->disable_reg_val = disable_reg_val;
wdt->mask_reset_reg_val = mask_reset_reg_val;
dev_info(wdt->dev, "Automatic_wdt set %s done, mask = %d\n", mask ? "true" : "false", mask);
return ret;
}
static void s3c2410wdt_mask_dbgack(struct s3c2410_wdt *wdt, bool mask)
{
unsigned long wtcon;
if (!(wdt->drv_data->quirks & QUIRK_HAS_DBGACK_BIT))
return;
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
if (mask)
wtcon |= EXYNOS_WTCON_DBGACK_MASK;
else
wtcon &= ~EXYNOS_WTCON_DBGACK_MASK;
writel(wtcon, wdt->reg_base + S3C2410_WTCON);
}
static void s3c2410wdt_gettime(int index)
{
struct s3c2410_wdt *wdt = s3c_wdt[index];
struct rtc_device *rtc;
wdt->tsk = current;
wdt->thr = current_thread_info();
wdt->last_ping_cpu = raw_smp_processor_id();
rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
if (!rtc) {
dev_info(wdt->dev, "Unable to open rtc device\n");
} else {
rtc_read_time(rtc, &wdt->tm);
rtc_class_close(rtc);
}
}
#if IS_ENABLED(CONFIG_SEC_DEBUG_WATCHDOGD_FOOTPRINT)
static ATOMIC_NOTIFIER_HEAD(secdbg_wdd_ping_notifier);
int secdbg_wdd_register_ping_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&secdbg_wdd_ping_notifier, nb);
}
EXPORT_SYMBOL_GPL(secdbg_wdd_register_ping_notifier);
#endif /* SEC_DEBUG_WATCHDOGD_FOOTPRINT */
static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
{
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
unsigned long flags, old_wtcnt = 0, wtcnt = 0;
if (wdt->cluster == LITTLE_CLUSTER)
s3c2410wdt_multistage_wdt_keepalive();
old_wtcnt = readl(wdt->reg_base + S3C2410_WTCNT);
spin_lock_irqsave(&wdt->lock, flags);
writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
spin_unlock_irqrestore(&wdt->lock, flags);
wtcnt = readl(wdt->reg_base + S3C2410_WTCNT);
dev_info(wdt->dev,
"Watchdog cluster %u keepalive!, old_wtcnt = %lx, wtcnt = %lx\n",
wdt->cluster, old_wtcnt, wtcnt);
if (!(wdt->in_panic || in_interrupt() || wdt->in_suspend
|| system_state == SYSTEM_POWER_OFF))
s3c2410wdt_gettime(wdt->cluster);
#if IS_ENABLED(CONFIG_SEC_DEBUG_WATCHDOGD_FOOTPRINT)
if (wdt->cluster == LITTLE_CLUSTER)
atomic_notifier_call_chain(&secdbg_wdd_ping_notifier, 0, wdt);
#endif
return 0;
}
static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt)
{
unsigned long wtcon;
/* If Cluster 0 Watchdog is disabled, Multistage watchdog is also disabled */
s3c2410wdt_multistage_wdt_stop();
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
writel(wtcon, wdt->reg_base + S3C2410_WTCON);
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
dev_info(wdt->dev, "Watchdog cluster %u stop done, WTCON = %lx\n", wdt->cluster, wtcon);
}
static int s3c2410wdt_stop(struct watchdog_device *wdd)
{
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
unsigned long flags;
spin_lock_irqsave(&wdt->lock, flags);
__s3c2410wdt_stop(wdt);
spin_unlock_irqrestore(&wdt->lock, flags);
return 0;
}
static int s3c2410wdt_stop_intclear(struct s3c2410_wdt *wdt)
{
unsigned long flags;
spin_lock_irqsave(&wdt->lock, flags);
__s3c2410wdt_stop(wdt);
writel(1, wdt->reg_base + S3C2410_WTCLRINT);
spin_unlock_irqrestore(&wdt->lock, flags);
return 0;
}
static int s3c2410wdt_start(struct watchdog_device *wdd)
{
unsigned long wtcon, wtmincnt, flags;
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
spin_lock_irqsave(&wdt->lock, flags);
__s3c2410wdt_stop(wdt);
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
if (soft_noboot) {
wtcon |= S3C2410_WTCON_INTEN;
wtcon &= ~S3C2410_WTCON_RSTEN;
} else {
wtcon &= ~S3C2410_WTCON_INTEN;
wtcon |= S3C2410_WTCON_RSTEN;
}
dev_dbg(wdt->dev, "Starting watchdog: count=0x%08x, wtcon=%08lx\n",
wdt->count, wtcon);
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG) {
wtcon |= EXYNOS_WTCON_WINDOWED_WD;
wtmincnt = wdt->count * WINDOW_MULTIPLIER;
writel(wtmincnt, wdt->reg_base + EXYNOS_WTMINCNT);
dev_dbg(wdt->dev, "Starting watchdog: wtmincnt=%08lx\n", wtmincnt);
}
writel(wdt->count, wdt->reg_base + S3C2410_WTDAT);
writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
writel(wtcon, wdt->reg_base + S3C2410_WTCON);
s3c2410wdt_multistage_wdt_start();
spin_unlock_irqrestore(&wdt->lock, flags);
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
dev_info(wdt->dev, "Watchdog cluster %u start, WTCON = %lx\n", wdt->cluster, wtcon);
return 0;
}
static inline int s3c2410wdt_is_running(struct s3c2410_wdt *wdt)
{
return readl(wdt->reg_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
}
static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd,
unsigned int timeout)
{
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
unsigned long freq = wdt->freq;
unsigned int count;
unsigned int divisor = 1;
unsigned long wtcon, wtmincnt;
if (timeout < 1)
return -EINVAL;
freq = DIV_ROUND_UP(freq, 128);
count = timeout * freq;
dev_dbg(wdt->dev, "Heartbeat: count=%d, timeout=%d, freq=%lu\n",
count, timeout, freq);
/* if the count is bigger than the watchdog register,
then work out what we need to do (and if) we can
actually make this value
*/
if (count >= 0x10000) {
divisor = DIV_ROUND_UP(count, 0xffff);
if (divisor > 0x100) {
dev_err(wdt->dev, "timeout %d too big\n", timeout);
divisor = 0x100;
}
}
dev_dbg(wdt->dev, "Heartbeat: timeout=%d, divisor=%d, count=%d (%08x)\n",
timeout, divisor, count, DIV_ROUND_UP(count, divisor));
count = DIV_ROUND_UP(count, divisor);
wdt->count = count;
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG) {
wtmincnt = count * WINDOW_MULTIPLIER;
writel(wtmincnt, wdt->reg_base + EXYNOS_WTMINCNT);
dev_dbg(wdt->dev, "Heartbeat: wtmincnt=%lx\n", wtmincnt);
}
/* update the pre-scaler */
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
writel(count, wdt->reg_base + S3C2410_WTDAT);
writel(wtcon, wdt->reg_base + S3C2410_WTCON);
wdd->timeout = (count * divisor) / freq;
s3c2410wdt_multistage_set_heartbeat(wdt, MULTISTAGE_WDT_RATIO);
return 0;
}
static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action,
void *data)
{
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
void __iomem *wdt_base = wdt->reg_base;
unsigned int wtcon;
/* disable watchdog, to be safe */
writel(0, wdt_base + S3C2410_WTCON);
/* put initial values into count and data */
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
writel(0x100, wdt_base + EXYNOS_WTMINCNT);
writel(0x80, wdt_base + S3C2410_WTCNT);
writel(0x80, wdt_base + S3C2410_WTDAT);
/* set the watchdog to go and reset... */
wtcon = S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 |
S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20);
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
wtcon |= EXYNOS_WTCON_WINDOWED_WD;
writel(wtcon, wdt_base + S3C2410_WTCON);
s3c2410wdt_multistage_wdt_start();
/* wait for reset to assert... */
mdelay(500);
return 0;
}
#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
static const struct watchdog_info s3c2410_wdt_ident = {
.options = OPTIONS,
.firmware_version = 0,
.identity = "S3C2410 Watchdog",
};
static const struct watchdog_ops s3c2410wdt_ops = {
.owner = THIS_MODULE,
.start = s3c2410wdt_start,
.stop = s3c2410wdt_stop,
.ping = s3c2410wdt_keepalive,
.set_timeout = s3c2410wdt_set_heartbeat,
.restart = s3c2410wdt_restart,
};
static const struct watchdog_device s3c2410_wdd = {
.info = &s3c2410_wdt_ident,
.ops = &s3c2410wdt_ops,
.timeout = S3C2410_WATCHDOG_DEFAULT_TIME,
};
/* interrupt handler code */
static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
{
struct s3c2410_wdt *wdt = platform_get_drvdata(param);
dev_info(wdt->dev, "watchdog timer expired (irq)\n");
s3c2410wdt_keepalive(&wdt->wdt_device);
if (wdt->drv_data->quirks & QUIRK_HAS_WTCLRINT_REG)
writel(0x1, wdt->reg_base + S3C2410_WTCLRINT);
return IRQ_HANDLED;
}
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
unsigned long val, void *data)
{
int ret;
struct s3c2410_wdt *wdt = freq_to_wdt(nb);
if (!s3c2410wdt_is_running(wdt))
goto done;
if (val == CPUFREQ_PRECHANGE) {
/* To ensure that over the change we don't cause the
* watchdog to trigger, we perform an keep-alive if
* the watchdog is running.
*/
s3c2410wdt_keepalive(&wdt->wdt_device);
} else if (val == CPUFREQ_POSTCHANGE) {
s3c2410wdt_stop(&wdt->wdt_device);
ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
wdt->wdt_device.timeout);
if (ret >= 0)
s3c2410wdt_start(&wdt->wdt_device);
else
goto err;
}
done:
return 0;
err:
dev_err(wdt->dev, "cannot set new value for timeout %d\n",
wdt->wdt_device.timeout);
return ret;
}
static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
{
wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
return cpufreq_register_notifier(&wdt->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
}
static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
{
wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
cpufreq_unregister_notifier(&wdt->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
}
#else
static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
{
return 0;
}
static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
{
}
#endif
static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt)
{
unsigned int rst_stat;
int ret;
if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT))
return 0;
ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat);
if (ret)
dev_warn(wdt->dev, "Couldn't get RST_STAT register\n");
else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit))
return WDIOF_CARDRESET;
return 0;
}
static inline const struct s3c2410_wdt_variant *
s3c2410_get_wdt_drv_data(struct platform_device *pdev)
{
const struct s3c2410_wdt_variant *variant;
variant = of_device_get_match_data(&pdev->dev);
if (!variant) {
/* Device matched by platform_device_id */
variant = (struct s3c2410_wdt_variant *)
platform_get_device_id(pdev)->driver_data;
}
return variant;
}
int s3c2410wdt_set_emergency_stop(int index)
{
struct s3c2410_wdt *wdt = s3c_wdt[index];
if (!wdt)
return -ENODEV;
/* stop watchdog */
dev_emerg(wdt->dev, "%s: watchdog is stopped\n", __func__);
s3c2410wdt_stop(&wdt->wdt_device);
return 0;
}
EXPORT_SYMBOL(s3c2410wdt_set_emergency_stop);
int s3c2410wdt_keepalive_emergency(bool reset, int index, int sec)
{
struct s3c2410_wdt *wdt = s3c_wdt[index];
if (!wdt)
return -ENODEV;
if (reset) {
dev_emerg(wdt->dev, "watchdog reset is started to %dsecs\n", sec);
s3c2410wdt_set_heartbeat(&wdt->wdt_device, sec);
s3c2410wdt_start(&wdt->wdt_device);
s3c2410wdt_multistage_wdt_stop();
}
s3c2410wdt_keepalive(&wdt->wdt_device);
return 0;
}
EXPORT_SYMBOL(s3c2410wdt_keepalive_emergency);
static int s3c2410wdt_get_multistage_index(void)
{
int i;
for (i = 0; i < MAX_WATCHDOG_CLUSTER_CNT; i++) {
if (!s3c_wdt[i])
continue;
if (s3c_wdt[i]->use_multistage_wdt)
return i;
}
return -ENODEV;
}
int s3c2410wdt_keepalive_common(void)
{
struct s3c2410_wdt *wdt = s3c_wdt[LITTLE_CLUSTER];
if (!wdt)
return -ENODEV;
return s3c2410wdt_keepalive(&wdt->wdt_device);
}
EXPORT_SYMBOL(s3c2410wdt_keepalive_common);
static int s3c2410wdt_multistage_set_heartbeat(struct s3c2410_wdt *wdt, int ratio)
{
int index, count;
unsigned int wtcon, multi_wtcon, wtmincnt;
index = s3c2410wdt_get_multistage_index();
if (index < 0)
return -ENODEV;
/* set the pre-scaler */
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
wtcon &= S3C2410_WTCON_PRESCALE_MASK;
multi_wtcon = readl(s3c_wdt[index]->reg_base + S3C2410_WTCON);
multi_wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
multi_wtcon |= wtcon;
count = (wdt->count * ratio) / 100;
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG) {
wtmincnt = count * WINDOW_MULTIPLIER;
writel(wtmincnt, s3c_wdt[index]->reg_base + EXYNOS_WTMINCNT);
}
writel(count, s3c_wdt[index]->reg_base + S3C2410_WTDAT);
writel(multi_wtcon, s3c_wdt[index]->reg_base + S3C2410_WTCON);
s3c_wdt[index]->count = count;
return 0;
}
static void s3c2410wdt_multistage_wdt_keepalive(void)
{
int index;
unsigned long flags, old_wtcnt = 0, wtcnt = 0;
index = s3c2410wdt_get_multistage_index();
if (index < 0)
return;
old_wtcnt = readl(s3c_wdt[index]->reg_base + S3C2410_WTCNT);
spin_lock_irqsave(&s3c_wdt[index]->lock, flags);
writel(s3c_wdt[index]->count, s3c_wdt[index]->reg_base + S3C2410_WTCNT);
spin_unlock_irqrestore(&s3c_wdt[index]->lock, flags);
wtcnt = readl(s3c_wdt[index]->reg_base + S3C2410_WTCNT);
dev_info(s3c_wdt[index]->dev,
"Watchdog cluster %u keepalive!, old_wtcnt = %lx, wtcnt = %lx\n",
s3c_wdt[index]->cluster, old_wtcnt, wtcnt);
if (!(s3c_wdt[index]->in_panic || in_interrupt() || s3c_wdt[index]->in_suspend
|| system_state == SYSTEM_POWER_OFF))
s3c2410wdt_gettime(index);
}
static int s3c2410wdt_multistage_wdt_stop(void)
{
int index;
unsigned long wtcon;
index = s3c2410wdt_get_multistage_index();
if (index < 0)
return -ENODEV;
wtcon = readl(s3c_wdt[index]->reg_base + S3C2410_WTCON);
wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_INTEN);
writel(wtcon, s3c_wdt[index]->reg_base + S3C2410_WTCON);
dev_info(s3c_wdt[index]->dev, "%s: cluster %u stop done, WTCON %08lx\n",
__func__, s3c_wdt[index]->cluster, wtcon);
return 0;
}
static int s3c2410wdt_multistage_wdt_start(void)
{
int index;
unsigned long wtcon;
index = s3c2410wdt_get_multistage_index();
if (index < 0)
return -ENODEV;
s3c2410wdt_multistage_wdt_stop();
wtcon = readl(s3c_wdt[index]->reg_base + S3C2410_WTCON);
wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
wtcon |= S3C2410_WTCON_INTEN;
wtcon &= ~S3C2410_WTCON_RSTEN;
if (s3c_wdt[index]->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG) {
wtcon |= EXYNOS_WTCON_WINDOWED_WD;
writel(s3c_wdt[index]->count * WINDOW_MULTIPLIER,
s3c_wdt[index]->reg_base + EXYNOS_WTMINCNT);
}
writel(s3c_wdt[index]->count, s3c_wdt[index]->reg_base + S3C2410_WTDAT);
writel(s3c_wdt[index]->count, s3c_wdt[index]->reg_base + S3C2410_WTCNT);
writel(wtcon, s3c_wdt[index]->reg_base + S3C2410_WTCON);
dev_info(s3c_wdt[index]->dev, "%s: count=0x%08x, wtcon=%08lx\n",
__func__, s3c_wdt[index]->count, wtcon);
return 0;
}
static struct wdt_panic_block {
struct notifier_block nb_panic_block;
struct s3c2410_wdt *wdt;
} wdt_block;
static int s3c2410wdt_panic_handler(struct notifier_block *nb,
unsigned long l, void *buf)
{
struct wdt_panic_block *wdt_panic = (struct wdt_panic_block *)nb;
struct s3c2410_wdt *wdt = wdt_panic->wdt;
int i, timeout;
if (!wdt)
return -ENODEV;
timeout = wdt->wdt_device.timeout;
for (i = 0; i < MAX_WATCHDOG_CLUSTER_CNT; i++) {
if (s3c_wdt[i])
s3c_wdt[i]->in_panic = 1;
}
/* We assumed that num_online_cpus() > 1 status is abnormal */
if (num_online_cpus() > 1)
timeout = 5;
s3c2410wdt_keepalive_emergency(true, 0, timeout);
return 0;
}
static int s3c2410wdt_reboot_handler(struct notifier_block *nb,
unsigned long mode, void *cmd)
{
if (!s3c_wdt[0])
return NOTIFY_DONE;
return NOTIFY_OK;
}
static struct notifier_block s3c2410wdt_nb_reboot_block = {
.notifier_call = s3c2410wdt_reboot_handler,
.priority = INT_MAX,
};
int s3c2410wdt_set_emergency_reset(unsigned int timeout_cnt, int index)
{
struct s3c2410_wdt *wdt = s3c_wdt[index];
unsigned int wtdat = 0;
unsigned int wtcnt = wtdat + timeout_cnt;
unsigned long wtcon;
if (!wdt)
return -ENODEV;
/* emergency reset with wdt reset */
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
wtcon |= S3C2410_WTCON_RSTEN | S3C2410_WTCON_ENABLE;
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
writel(wtdat * WINDOW_MULTIPLIER, wdt->reg_base + EXYNOS_WTMINCNT);
writel(wtdat, wdt->reg_base + S3C2410_WTDAT);
writel(wtcnt, wdt->reg_base + S3C2410_WTCNT);
writel(wtcon, wdt->reg_base + S3C2410_WTCON);
return 0;
}
inline int s3c2410wdt_multistage_emergency_reset(unsigned int timeout_cnt)
{
unsigned int wtdat = 0;
unsigned int wtcnt = wtdat + timeout_cnt;
unsigned long wtcon;
int index;
index = s3c2410wdt_get_multistage_index();
if (index < 0)
return -ENODEV;
/* emergency reset with wdt reset */
wtcon = readl(s3c_wdt[index]->reg_base + S3C2410_WTCON);
wtcon |= S3C2410_WTCON_INTEN | S3C2410_WTCON_ENABLE;
if (s3c_wdt[index]->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
writel(wtdat * WINDOW_MULTIPLIER,
s3c_wdt[index]->reg_base + EXYNOS_WTMINCNT);
writel(wtdat, s3c_wdt[index]->reg_base + S3C2410_WTDAT);
writel(wtcnt, s3c_wdt[index]->reg_base + S3C2410_WTCNT);
writel(wtcon, s3c_wdt[index]->reg_base + S3C2410_WTCON);
return 0;
}
inline void s3c2410wdt_reset_confirm(unsigned long mtime, int index)
{
struct s3c2410_wdt *wdt = s3c_wdt[index];
unsigned int wtcon, wtdat, wtcnt, disable_reg, mask_reset_reg;
unsigned long total_time = 0;
int ret;
if (!wdt)
return;
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
dev_info(wdt->dev, "Current %s_cluster watchdog %sable, wtcon = %x\n",
index ? "Big" : "Little",
(wtcon & S3C2410_WTCON_ENABLE) ? "en" : "dis", wtcon);
ret = regmap_read(wdt->pmureg, wdt->drv_data->mask_reset_reg, &mask_reset_reg);
if (ret) {
dev_err(wdt->dev, "Couldn't get MASK_WDT_RESET register\n");
return;
}
ret = regmap_read(wdt->pmureg, wdt->drv_data->disable_reg, &disable_reg);
if (ret) {
dev_err(wdt->dev, "Couldn't get DISABLE_WDT register\n");
return;
}
/* Fake watchdog bits in both registers must be cleared. */
dev_info(wdt->dev, "DISABLE_WDT reg: %x, MASK_WDT_RESET reg: %x\n", disable_reg, mask_reset_reg);
/* If watchdog is disabled, do not print wtcnt value. */
if(!(wtcon & S3C2410_WTCON_ENABLE))
return;
do {
/* It continues to print the wtcnt and wddat values
* until watchdog reset is taken. */
wtdat = readl(wdt->reg_base + S3C2410_WTDAT);
wtcnt = readl(wdt->reg_base + S3C2410_WTCNT);
dev_info(wdt->dev, "%lu milliseconds, wtdat = %u, wtcnt = %u",
total_time, wtdat, wtcnt);
total_time += mtime;
mdelay(mtime);
} while(1);
/* This function does not return. */
}
EXPORT_SYMBOL(s3c2410wdt_reset_confirm);
#ifdef CONFIG_PM_SLEEP
static int s3c2410wdt_dev_suspend(struct device *dev)
{
struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
if (!wdt)
return 0;
/* little cluster wdt should excute syscore suspend */
if (wdt->cluster == LITTLE_CLUSTER)
return 0;
wdt->in_suspend = 1;
s3c2410wdt_keepalive(&wdt->wdt_device);
/* Save watchdog state, and turn it off. */
wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
return 0;
}
static int s3c2410wdt_dev_resume(struct device *dev)
{
int ret = 0;
unsigned int val, wtmincnt;
struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
if (!wdt)
return ret;
/* little cluster wdt should excute syscore resume */
if (wdt->cluster == LITTLE_CLUSTER)
return ret;
wdt->in_suspend = 0;
if (wdt->drv_data->auto_disable_func) {
ret = wdt->drv_data->auto_disable_func(wdt, false);
if (ret < 0) {
dev_info(wdt->dev, "automatic_dsiable fail");
return ret;
}
}
s3c2410wdt_stop_intclear(wdt);
/* Restore watchdog state. */
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG) {
wtmincnt = wdt->wtdat_save * WINDOW_MULTIPLIER;
writel(wtmincnt, wdt->reg_base + EXYNOS_WTMINCNT);
}
writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT);
writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
if ((!wdt->use_multistage_wdt) && (wdt->drv_data->pmu_reset_func)) {
ret = wdt->drv_data->pmu_reset_func(wdt, false);
if (ret < 0) {
dev_info(wdt->dev, "wdt reset mask fail");
return ret;
}
}
if (wdt->drv_data->pmu_count_en_func) {
ret = wdt->drv_data->pmu_count_en_func(wdt, true);
if (ret < 0) {
dev_info(wdt->dev, "wdt count enable fail\n");
return ret;
}
}
val = readl(wdt->reg_base + S3C2410_WTCON);
dev_info(wdt->dev, "watchdog %sabled, con: 0x%08x, dat: 0x%08x, cnt: 0x%08x\n",
(val & S3C2410_WTCON_ENABLE) ? "en" : "dis", val,
readl(wdt->reg_base + S3C2410_WTDAT),
readl(wdt->reg_base + S3C2410_WTCNT));
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
dev_info(wdt->dev, "windowed_wd %sabled, wtmincnt: 0x%08x\n",
(wdt->wtcon_save & EXYNOS_WTCON_WINDOWED_WD) ? "en" : "dis", wtmincnt);
return ret;
}
#else
#define s3c2410wdt_dev_suspend NULL
#define s3c2410wdt_dev_resume NULL
#endif
static SIMPLE_DEV_PM_OPS(s3c_wdt_pm_ops, s3c2410wdt_dev_suspend, s3c2410wdt_dev_resume);
#ifdef CONFIG_PM
static int s3c2410wdt_syscore_suspend(void)
{
struct s3c2410_wdt *wdt = s3c_wdt[LITTLE_CLUSTER];
if (!wdt)
return 0;
wdt->in_suspend = 1;
s3c2410wdt_keepalive(&wdt->wdt_device);
/* Save watchdog state, and turn it off. */
wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
return 0;
}
static void s3c2410wdt_syscore_resume(void)
{
int ret;
unsigned int val;
struct s3c2410_wdt *wdt = s3c_wdt[LITTLE_CLUSTER];
if (!wdt)
return;
wdt->in_suspend = 0;
if (wdt->drv_data->auto_disable_func) {
ret = wdt->drv_data->auto_disable_func(wdt, false);
if (ret < 0) {
dev_info(wdt->dev, "automatic_dsiable fail");
return;
}
}
s3c2410wdt_stop_intclear(wdt);
/* Restore watchdog state. */
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
writel(wdt->wtdat_save * WINDOW_MULTIPLIER, wdt->reg_base + EXYNOS_WTMINCNT);
writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT);
writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
if ((!wdt->use_multistage_wdt) && (wdt->drv_data->pmu_reset_func)) {
ret = wdt->drv_data->pmu_reset_func(wdt, false);
if (ret < 0) {
dev_info(wdt->dev, "pmu reset function fail");
return;
}
}
if (wdt->drv_data->pmu_count_en_func) {
ret = wdt->drv_data->pmu_count_en_func(wdt, true);
if (ret < 0) {
dev_info(wdt->dev, "wdt count enable fail\n");
return;
}
}
val = readl(wdt->reg_base + S3C2410_WTCON);
dev_info(wdt->dev, "watchdog %sabled, con: 0x%08x, dat: 0x%08x, cnt: 0x%08x\n",
(val & S3C2410_WTCON_ENABLE) ? "en" : "dis", val,
readl(wdt->reg_base + S3C2410_WTDAT),
readl(wdt->reg_base + S3C2410_WTCNT));
}
#else
#define s3c2410_wdt_syscore_suspend NULL
#define s3c2410_wdt_syscore_resume NULL
#endif
static struct syscore_ops s3c2410wdt_syscore_ops = {
.suspend = s3c2410wdt_syscore_suspend,
.resume = s3c2410wdt_syscore_resume,
};
static int s3c2410wdt_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *v)
{
struct s3c2410_wdt *wdt = s3c_wdt[LITTLE_CLUSTER];
switch (pm_event) {
case PM_SUSPEND_PREPARE:
s3c2410wdt_keepalive(&wdt->wdt_device);
break;
}
return NOTIFY_OK;
}
static struct notifier_block s3c2410wdt_pm_nb = {
.notifier_call = s3c2410wdt_pm_notifier,
.priority = 0,
};
static int s3c2410wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct s3c2410_wdt *wdt;
struct resource *wdt_irq;
unsigned int wtcon, disable_reg_val = 0, mask_reset_reg_val = 0;
unsigned int noncpu_int_reg_val = 0, noncpu_out_reg_val = 0;
int started = 0;
int ret, cluster_index;
wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
if (!wdt)
return -ENOMEM;
wdt->dev = dev;
spin_lock_init(&wdt->lock);
wdt->wdt_device = s3c2410_wdd;
if (of_property_read_u32(dev->of_node, "index", &cluster_index)) {
dev_err(dev, "Watchdog cluster index lookup failed.\n");
return -EINVAL;
}
dev_info(dev, "watchdog cluster%d probe\n", cluster_index);
/* s3c_wdt[0] = Little cluster wdt, s3c_wdt[1] = Big cluster wdt */
if (cluster_index >= MAX_WATCHDOG_CLUSTER_CNT) {
dev_err(dev, "Watchdog index property too large.\n");
return -EINVAL;
}
s3c_wdt[cluster_index] = wdt;
wdt->cluster = cluster_index;
wdt->drv_data = s3c2410_get_wdt_drv_data(pdev);
if (of_find_property(dev->of_node, "use_multistage_wdt", NULL)) {
/* If use multistage watchdog, value is 1*/
wdt->use_multistage_wdt = true;
} else {
/* Not use Multistage watchdog */
wdt->use_multistage_wdt = false;
dev_info(dev, "It is not a multistage watchdog.\n");
}
if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) {
wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
"samsung,syscon-phandle");
if (IS_ERR(wdt->pmureg)) {
dev_err(dev, "syscon regmap lookup failed.\n");
return PTR_ERR(wdt->pmureg);
}
}
if (wdt->drv_data->mask_reset_reg) {
ret = regmap_read(wdt->pmureg, wdt->drv_data->mask_reset_reg, &mask_reset_reg_val);
if (ret) {
dev_err(wdt->dev, "Couldn't get MASK_WDT_RESET register\n");
return PTR_ERR(wdt->pmureg);
}
dev_info(wdt->dev, "MASK_WDT_RESET reg val: %x\n", mask_reset_reg_val);
}
if (wdt->drv_data->disable_reg) {
ret = regmap_read(wdt->pmureg, wdt->drv_data->disable_reg, &disable_reg_val);
if (ret) {
dev_err(wdt->dev, "Couldn't get DISABLE_WDT register\n");
return PTR_ERR(wdt->pmureg);
}
dev_info(wdt->dev, "DISABLE_WDT reg val: %x\n", disable_reg_val);
}
if (wdt->drv_data->noncpu_int_en) {
ret = regmap_read(wdt->pmureg, wdt->drv_data->noncpu_int_en, &noncpu_int_reg_val);
if (ret) {
dev_err(wdt->dev, "Couldn't get NONCPU_INT_EN register\n");
return PTR_ERR(wdt->pmureg);
}
dev_info(wdt->dev, "NONCPU_INT_EN reg val: %x\n", noncpu_int_reg_val);
}
wdt->disable_reg_val = disable_reg_val;
wdt->mask_reset_reg_val = mask_reset_reg_val;
wdt->noncpu_int_reg_val = noncpu_int_reg_val;
wdt->noncpu_out_reg_val = noncpu_out_reg_val;
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (wdt_irq == NULL) {
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err;
}
/* get the memory region for the watchdog timer */
wdt->reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(wdt->reg_base)) {
ret = PTR_ERR(wdt->reg_base);
goto err;
}
dev_info(dev, "probe: mapped reg_base=%p\n", wdt->reg_base);
wdt->rate_clock = devm_clk_get(dev, "rate_watchdog");
if (IS_ERR(wdt->rate_clock)) {
dev_err(dev, "failed to find watchdog rate clock source\n");
ret = PTR_ERR(wdt->rate_clock);
goto err;
}
wdt->freq = clk_get_rate(wdt->rate_clock);
wdt->gate_clock = devm_clk_get(dev, "gate_watchdog");
if (IS_ERR(wdt->gate_clock)) {
dev_err(dev, "failed to find watchdog gate clock source\n");
ret = PTR_ERR(wdt->gate_clock);
goto err;
}
ret = clk_prepare_enable(wdt->gate_clock);
if (ret < 0) {
dev_err(dev, "failed to enable gate clock\n");
return ret;
}
if (!of_property_read_u32(dev->of_node, "shutdown_wdt_timeout",
&wdt->shutdown_timeout)) {
dev_info(dev, "Shutdown Watchdog timeout value is: %d\n", wdt->shutdown_timeout);
} else {
wdt->shutdown_timeout = 30;
dev_info(dev, "Shutdown timeout value is not defined. We set it for 30secs\n");
}
wdt->wdt_device.min_timeout = 1;
wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt->rate_clock);
ret = s3c2410wdt_cpufreq_register(wdt);
if (ret < 0) {
dev_err(dev, "failed to register cpufreq\n");
goto err_clk;
}
watchdog_set_drvdata(&wdt->wdt_device, wdt);
/* see if we can actually set the requested timer margin, and if
* not, try the default value */
watchdog_init_timeout(&wdt->wdt_device, tmr_margin, dev);
ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
wdt->wdt_device.timeout);
if (ret) {
started = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
S3C2410_WATCHDOG_DEFAULT_TIME);
if (started == 0)
dev_info(dev,
"tmr_margin value out of range, default %d used\n",
S3C2410_WATCHDOG_DEFAULT_TIME);
else
dev_info(dev, "default timer value is out of range, cannot start\n");
}
ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0,
pdev->name, pdev);
if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_cpufreq;
}
watchdog_set_nowayout(&wdt->wdt_device, nowayout);
watchdog_set_restart_priority(&wdt->wdt_device, 64);
wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt);
wdt->wdt_device.parent = dev;
if (cluster_index == LITTLE_CLUSTER) {
ret = watchdog_register_device(&wdt->wdt_device);
if (ret) {
dev_err(dev, "cannot register watchdog (%d)\n", ret);
goto err_cpufreq;
}
}
if (wdt->drv_data->auto_disable_func) {
ret = wdt->drv_data->auto_disable_func(wdt, false);
if (ret < 0) {
dev_info(wdt->dev, "automatic_dsiable fail");
return 0;
}
}
/* Stop watchdog reset or Keepalive watchdog while setting */
if (!tmr_atboot)
s3c2410wdt_stop_intclear(wdt);
else
s3c2410wdt_keepalive(&wdt->wdt_device);
if (wdt->drv_data->pmu_count_en_func) {
ret = wdt->drv_data->pmu_count_en_func(wdt, true);
if (ret < 0)
goto err_unregister;
}
if ((!wdt->use_multistage_wdt) && (wdt->drv_data->pmu_reset_func)) {
ret = wdt->drv_data->pmu_reset_func(wdt, false);
if (ret < 0)
goto err_unregister;
}
s3c2410wdt_mask_dbgack(wdt, true);
if (tmr_atboot && started == 0) {
dev_info(dev, "starting watchdog timer\n");
s3c2410wdt_start(&wdt->wdt_device);
} else if (!tmr_atboot) {
/* if we're not enabling the watchdog, then ensure it is
* disabled if it has been left running from the bootloader
* or other source */
s3c2410wdt_stop(&wdt->wdt_device);
}
platform_set_drvdata(pdev, wdt);
/* print out a statement of readiness */
wtcon = readl(wdt->reg_base + S3C2410_WTCON);
if (cluster_index == LITTLE_CLUSTER) {
register_syscore_ops(&s3c2410wdt_syscore_ops);
/* register panic handler for watchdog reset */
wdt_block.nb_panic_block.notifier_call = s3c2410wdt_panic_handler;
wdt_block.nb_panic_block.priority = 256;
wdt_block.wdt = wdt;
atomic_notifier_chain_register(&panic_notifier_list,
&wdt_block.nb_panic_block);
dbg_snapshot_register_wdt_ops(
(void *)s3c2410wdt_keepalive_emergency,
(void *)s3c2410wdt_set_emergency_reset,
(void *)s3c2410wdt_set_emergency_stop);
register_reboot_notifier(&s3c2410wdt_nb_reboot_block);
register_pm_notifier(&s3c2410wdt_pm_nb);
}
dev_info(dev, "watchdog cluster %d, %sactive, reset %sabled, irq %sabled\n",
cluster_index,
(wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
(wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
(wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
if (wdt->drv_data->quirks & QUIRK_HAS_DBGACK_BIT)
dev_info(dev, "DBGACK %sabled\n",
(wtcon & EXYNOS_WTCON_DBGACK_MASK) ? "en" : "dis");
if (wdt->drv_data->quirks & QUIRK_HAS_WTMINCNT_REG)
dev_info(dev, "windowed watchdog %sabled, wtmincnt=%x\n",
(wtcon & EXYNOS_WTCON_WINDOWED_WD) ? "en" : "dis",
readl(wdt->reg_base + EXYNOS_WTMINCNT));
pr_info("Multistage watchdog %sabled",
wdt->use_multistage_wdt ? "en" : "dis");
return 0;
err_unregister:
watchdog_unregister_device(&wdt->wdt_device);
err_cpufreq:
s3c2410wdt_cpufreq_deregister(wdt);
err_clk:
clk_disable_unprepare(wdt->gate_clock);
wdt->rate_clock = NULL;
wdt->gate_clock = NULL;
err:
return ret;
}
static int s3c2410wdt_remove(struct platform_device *dev)
{
int ret = 0;
struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
if (wdt->drv_data->pmu_reset_func)
ret = wdt->drv_data->pmu_reset_func(wdt, true);
if (ret < 0)
return ret;
watchdog_unregister_device(&wdt->wdt_device);
s3c2410wdt_cpufreq_deregister(wdt);
clk_disable_unprepare(wdt->rate_clock);
wdt->rate_clock = NULL;
wdt->gate_clock = NULL;
unregister_pm_notifier(&s3c2410wdt_pm_nb);
return ret;
}
static void s3c2410wdt_shutdown(struct platform_device *dev)
{
struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
#ifdef CONFIG_S3C2410_SHUTDOWN_REBOOT
dev_emerg(wdt->dev, "%s: watchdog is still alive\n", __func__);
s3c2410wdt_keepalive_emergency(true, 0, wdt->shutdown_timeout);
#else
/* Only little cluster watchdog excute mask function */
if ((wdt->cluster == LITTLE_CLUSTER) && (wdt->drv_data->pmu_reset_func))
wdt->drv_data->pmu_reset_func(wdt, true);
s3c2410wdt_stop(&wdt->wdt_device);
#endif
}
static struct platform_driver s3c2410wdt_driver = {
.probe = s3c2410wdt_probe,
.remove = s3c2410wdt_remove,
.shutdown = s3c2410wdt_shutdown,
.id_table = s3c2410_wdt_ids,
.driver = {
.name = "s3c2410-wdt",
.pm = &s3c_wdt_pm_ops,
.of_match_table = of_match_ptr(s3c2410_wdt_match),
},
};
int __init s3c2410wdt_init(void)
{
return platform_driver_register(&s3c2410wdt_driver);
}
subsys_initcall(s3c2410wdt_init);
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Dimitry Andric <dimitry.andric@tomtom.com>");
MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
MODULE_LICENSE("GPL");