summaryrefslogtreecommitdiff
path: root/drivers/watchdog
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog')
-rw-r--r--drivers/watchdog/aspeed_wdt.c81
-rw-r--r--drivers/watchdog/da9052_wdt.c1
-rw-r--r--drivers/watchdog/dw_wdt.c2
-rw-r--r--drivers/watchdog/exar_wdt.c2
-rw-r--r--drivers/watchdog/iTCO_wdt.c6
-rw-r--r--drivers/watchdog/rti_wdt.c1
-rw-r--r--drivers/watchdog/sbsa_gwdt.c50
-rw-r--r--drivers/watchdog/ziirave_wdt.c3
8 files changed, 139 insertions, 7 deletions
diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c
index b4773a6aaf8c..837e15701c0e 100644
--- a/drivers/watchdog/aspeed_wdt.c
+++ b/drivers/watchdog/aspeed_wdt.c
@@ -11,21 +11,30 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/kstrtox.h>
+#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
#include <linux/watchdog.h>
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+struct aspeed_wdt_scu {
+ const char *compatible;
+ u32 reset_status_reg;
+ u32 wdt_reset_mask;
+ u32 wdt_reset_mask_shift;
+};
struct aspeed_wdt_config {
u32 ext_pulse_width_mask;
u32 irq_shift;
u32 irq_mask;
+ struct aspeed_wdt_scu scu;
};
struct aspeed_wdt {
@@ -39,18 +48,36 @@ static const struct aspeed_wdt_config ast2400_config = {
.ext_pulse_width_mask = 0xff,
.irq_shift = 0,
.irq_mask = 0,
+ .scu = {
+ .compatible = "aspeed,ast2400-scu",
+ .reset_status_reg = 0x3c,
+ .wdt_reset_mask = 0x1,
+ .wdt_reset_mask_shift = 1,
+ },
};
static const struct aspeed_wdt_config ast2500_config = {
.ext_pulse_width_mask = 0xfffff,
.irq_shift = 12,
.irq_mask = GENMASK(31, 12),
+ .scu = {
+ .compatible = "aspeed,ast2500-scu",
+ .reset_status_reg = 0x3c,
+ .wdt_reset_mask = 0x1,
+ .wdt_reset_mask_shift = 2,
+ },
};
static const struct aspeed_wdt_config ast2600_config = {
.ext_pulse_width_mask = 0xfffff,
.irq_shift = 0,
.irq_mask = GENMASK(31, 10),
+ .scu = {
+ .compatible = "aspeed,ast2600-scu",
+ .reset_status_reg = 0x74,
+ .wdt_reset_mask = 0xf,
+ .wdt_reset_mask_shift = 16,
+ },
};
static const struct of_device_id aspeed_wdt_of_table[] = {
@@ -213,6 +240,56 @@ static int aspeed_wdt_restart(struct watchdog_device *wdd,
return 0;
}
+static void aspeed_wdt_update_bootstatus(struct platform_device *pdev,
+ struct aspeed_wdt *wdt)
+{
+ const struct resource *res;
+ struct aspeed_wdt_scu scu = wdt->cfg->scu;
+ struct regmap *scu_base;
+ u32 reset_mask_width;
+ u32 reset_mask_shift;
+ u32 idx = 0;
+ u32 status;
+ int ret;
+
+ if (!of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2400-wdt")) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ idx = ((intptr_t)wdt->base & 0x00000fff) / (uintptr_t)resource_size(res);
+ }
+
+ scu_base = syscon_regmap_lookup_by_compatible(scu.compatible);
+ if (IS_ERR(scu_base)) {
+ wdt->wdd.bootstatus = WDIOS_UNKNOWN;
+ return;
+ }
+
+ ret = regmap_read(scu_base, scu.reset_status_reg, &status);
+ if (ret) {
+ wdt->wdd.bootstatus = WDIOS_UNKNOWN;
+ return;
+ }
+
+ reset_mask_width = hweight32(scu.wdt_reset_mask);
+ reset_mask_shift = scu.wdt_reset_mask_shift +
+ reset_mask_width * idx;
+
+ if (status & (scu.wdt_reset_mask << reset_mask_shift))
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
+
+ /* clear wdt reset event flag */
+ if (of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2400-wdt") ||
+ of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2500-wdt")) {
+ ret = regmap_read(scu_base, scu.reset_status_reg, &status);
+ if (!ret) {
+ status &= ~(scu.wdt_reset_mask << reset_mask_shift);
+ regmap_write(scu_base, scu.reset_status_reg, status);
+ }
+ } else {
+ regmap_write(scu_base, scu.reset_status_reg,
+ scu.wdt_reset_mask << reset_mask_shift);
+ }
+}
+
/* access_cs0 shows if cs0 is accessible, hence the reverted bit */
static ssize_t access_cs0_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -458,10 +535,10 @@ static int aspeed_wdt_probe(struct platform_device *pdev)
writel(duration - 1, wdt->base + WDT_RESET_WIDTH);
}
+ aspeed_wdt_update_bootstatus(pdev, wdt);
+
status = readl(wdt->base + WDT_TIMEOUT_STATUS);
if (status & WDT_TIMEOUT_STATUS_BOOT_SECONDARY) {
- wdt->wdd.bootstatus = WDIOF_CARDRESET;
-
if (of_device_is_compatible(np, "aspeed,ast2400-wdt") ||
of_device_is_compatible(np, "aspeed,ast2500-wdt"))
wdt->wdd.groups = bswitch_groups;
diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c
index d708c091bf1b..180526220d8c 100644
--- a/drivers/watchdog/da9052_wdt.c
+++ b/drivers/watchdog/da9052_wdt.c
@@ -164,6 +164,7 @@ static int da9052_wdt_probe(struct platform_device *pdev)
da9052_wdt = &driver_data->wdt;
da9052_wdt->timeout = DA9052_DEF_TIMEOUT;
+ da9052_wdt->min_hw_heartbeat_ms = DA9052_TWDMIN;
da9052_wdt->info = &da9052_wdt_info;
da9052_wdt->ops = &da9052_wdt_ops;
da9052_wdt->parent = dev;
diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
index 84dca3695f86..e5e6d7f15918 100644
--- a/drivers/watchdog/dw_wdt.c
+++ b/drivers/watchdog/dw_wdt.c
@@ -644,6 +644,8 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
} else {
wdd->timeout = DW_WDT_DEFAULT_SECONDS;
watchdog_init_timeout(wdd, 0, dev);
+ /* Limit timeout value to hardware constraints. */
+ dw_wdt_set_timeout(wdd, wdd->timeout);
}
platform_set_drvdata(pdev, dw_wdt);
diff --git a/drivers/watchdog/exar_wdt.c b/drivers/watchdog/exar_wdt.c
index 7c61ff343271..c2e3bb08df89 100644
--- a/drivers/watchdog/exar_wdt.c
+++ b/drivers/watchdog/exar_wdt.c
@@ -221,7 +221,7 @@ static const struct watchdog_info exar_wdt_info = {
.options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE,
- .identity = "Exar/MaxLinear XR28V38x Watchdog",
+ .identity = "Exar XR28V38x Watchdog",
};
static const struct watchdog_ops exar_wdt_ops = {
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
index f01ed38aba67..d5c91759dc00 100644
--- a/drivers/watchdog/iTCO_wdt.c
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -601,7 +601,11 @@ static int iTCO_wdt_probe(struct platform_device *pdev)
/* Check that the heartbeat value is within it's range;
if not reset to the default */
if (iTCO_wdt_set_timeout(&p->wddev, heartbeat)) {
- iTCO_wdt_set_timeout(&p->wddev, WATCHDOG_TIMEOUT);
+ ret = iTCO_wdt_set_timeout(&p->wddev, WATCHDOG_TIMEOUT);
+ if (ret != 0) {
+ dev_err(dev, "Failed to set watchdog timeout (%d)\n", WATCHDOG_TIMEOUT);
+ return ret;
+ }
dev_info(dev, "timeout value out of range, using %d\n",
WATCHDOG_TIMEOUT);
}
diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c
index 563d842014df..cc239251e193 100644
--- a/drivers/watchdog/rti_wdt.c
+++ b/drivers/watchdog/rti_wdt.c
@@ -301,6 +301,7 @@ static int rti_wdt_probe(struct platform_device *pdev)
node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
if (node) {
ret = of_address_to_resource(node, 0, &res);
+ of_node_put(node);
if (ret) {
dev_err(dev, "No memory address assigned to the region.\n");
goto err_iomap;
diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c
index 5f23913ce3b4..6ce1bfb39064 100644
--- a/drivers/watchdog/sbsa_gwdt.c
+++ b/drivers/watchdog/sbsa_gwdt.c
@@ -75,11 +75,17 @@
#define SBSA_GWDT_VERSION_MASK 0xF
#define SBSA_GWDT_VERSION_SHIFT 16
+#define SBSA_GWDT_IMPL_MASK 0x7FF
+#define SBSA_GWDT_IMPL_SHIFT 0
+#define SBSA_GWDT_IMPL_MEDIATEK 0x426
+
/**
* struct sbsa_gwdt - Internal representation of the SBSA GWDT
* @wdd: kernel watchdog_device structure
* @clk: store the System Counter clock frequency, in Hz.
* @version: store the architecture version
+ * @need_ws0_race_workaround:
+ * indicate whether to adjust wdd->timeout to avoid a race with WS0
* @refresh_base: Virtual address of the watchdog refresh frame
* @control_base: Virtual address of the watchdog control frame
*/
@@ -87,6 +93,7 @@ struct sbsa_gwdt {
struct watchdog_device wdd;
u32 clk;
int version;
+ bool need_ws0_race_workaround;
void __iomem *refresh_base;
void __iomem *control_base;
};
@@ -161,6 +168,31 @@ static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
*/
sbsa_gwdt_reg_write(((u64)gwdt->clk / 2) * timeout, gwdt);
+ /*
+ * Some watchdog hardware has a race condition where it will ignore
+ * sbsa_gwdt_keepalive() if it is called at the exact moment that a
+ * timeout occurs and WS0 is being asserted. Unfortunately, the default
+ * behavior of the watchdog core is very likely to trigger this race
+ * when action=0 because it programs WOR to be half of the desired
+ * timeout, and watchdog_next_keepalive() chooses the exact same time to
+ * send keepalive pings.
+ *
+ * This triggers a race where sbsa_gwdt_keepalive() can be called right
+ * as WS0 is being asserted, and affected hardware will ignore that
+ * write and continue to assert WS0. After another (timeout / 2)
+ * seconds, the same race happens again. If the driver wins then the
+ * explicit refresh will reset WS0 to false but if the hardware wins,
+ * then WS1 is asserted and the system resets.
+ *
+ * Avoid the problem by scheduling keepalive heartbeats one second later
+ * than the WOR timeout.
+ *
+ * This workaround might not be needed in a future revision of the
+ * hardware.
+ */
+ if (gwdt->need_ws0_race_workaround)
+ wdd->min_hw_heartbeat_ms = timeout * 500 + 1000;
+
return 0;
}
@@ -202,12 +234,15 @@ static int sbsa_gwdt_keepalive(struct watchdog_device *wdd)
static void sbsa_gwdt_get_version(struct watchdog_device *wdd)
{
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
- int ver;
+ int iidr, ver, impl;
- ver = readl(gwdt->control_base + SBSA_GWDT_W_IIDR);
- ver = (ver >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK;
+ iidr = readl(gwdt->control_base + SBSA_GWDT_W_IIDR);
+ ver = (iidr >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK;
+ impl = (iidr >> SBSA_GWDT_IMPL_SHIFT) & SBSA_GWDT_IMPL_MASK;
gwdt->version = ver;
+ gwdt->need_ws0_race_workaround =
+ !action && (impl == SBSA_GWDT_IMPL_MEDIATEK);
}
static int sbsa_gwdt_start(struct watchdog_device *wdd)
@@ -299,6 +334,15 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
else
wdd->max_hw_heartbeat_ms = GENMASK_ULL(47, 0) / gwdt->clk * 1000;
+ if (gwdt->need_ws0_race_workaround) {
+ /*
+ * A timeout of 3 seconds means that WOR will be set to 1.5
+ * seconds and the heartbeat will be scheduled every 2.5
+ * seconds.
+ */
+ wdd->min_timeout = 3;
+ }
+
status = readl(cf_base + SBSA_GWDT_WCS);
if (status & SBSA_GWDT_WCS_WS1) {
dev_warn(dev, "System reset by WDT.\n");
diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c
index 775838346bb5..09d6721c7bfa 100644
--- a/drivers/watchdog/ziirave_wdt.c
+++ b/drivers/watchdog/ziirave_wdt.c
@@ -302,6 +302,9 @@ static int ziirave_firm_verify(struct watchdog_device *wdd,
const u16 len = be16_to_cpu(rec->len);
const u32 addr = be32_to_cpu(rec->addr);
+ if (len > sizeof(data))
+ return -EINVAL;
+
if (ziirave_firm_addr_readonly(addr))
continue;