[PATCH 4/5] watchdog: eiois200_wdt: Enhanced watchdog functionality and pretimeout

From: advantech . susiteam
Date: Thu Oct 05 2023 - 10:38:48 EST


From: Wenkai <advantech.susiteam@xxxxxxxxx>

This patch extends the Advantech EIO-IS200 Watchdog Driver to provide
advanced watchdog functionality, including pretimeout support. It allows
the user to specify a timeout or pre-timeout trigger event, let the
event pin output level switches from high to low. The event pin which
can be one of the following:
- PWRBTN (Power button)
- SCI (ACPI System Control Interrupt)
- IRQ
- GPIO

If the pretimeout is specified, when the pretimeout time expires, it
triggers the associated pin. If the timeout expires, it triggers a reset.

If the pretimeout is not specified, the timeout expiration triggers the
associated pin or the reset pin.

This addition to basic watchdog functionality. It ensures proper
integration with the watchdog framework and provides a flexible watchdog
solution for Advantech EIO-IS200-based systems.

Signed-off-by: Wenkai <advantech.susiteam@xxxxxxxxx>
---
drivers/watchdog/eiois200_wdt.c | 159 ++++++++++++++++++++++++++++++--
1 file changed, 152 insertions(+), 7 deletions(-)

diff --git a/drivers/watchdog/eiois200_wdt.c b/drivers/watchdog/eiois200_wdt.c
index 569e619448e5..85179806ab7e 100644
--- a/drivers/watchdog/eiois200_wdt.c
+++ b/drivers/watchdog/eiois200_wdt.c
@@ -18,13 +18,21 @@

/* Support Flags */
#define SUPPORT_AVAILABLE BIT(0)
+#define SUPPORT_PWRBTN BIT(3)
+#define SUPPORT_IRQ BIT(4)
+#define SUPPORT_SCI BIT(5)
+#define SUPPORT_PIN BIT(6)
#define SUPPORT_RESET BIT(7)

/* PMC registers */
#define REG_STATUS 0x00
#define REG_CONTROL 0x02
#define REG_EVENT 0x10
+#define REG_PWR_EVENT_TIME 0x12
+#define REG_IRQ_EVENT_TIME 0x13
#define REG_RESET_EVENT_TIME 0x14
+#define REG_PIN_EVENT_TIME 0x15
+#define REG_SCI_EVENT_TIME 0x16
#define REG_IRQ_NUMBER 0x17

/* PMC command and control */
@@ -50,20 +58,53 @@
#define PMC_WRITE(cmd, data) pmc(CMD_WDT_WRITE, cmd, data)
#define PMC_READ(cmd, data) pmc(CMD_WDT_READ, cmd, data)

+/* Mapping event type to supported bit */
+#define EVENT_BIT(type) BIT(type + 2)
+
+enum event_type {
+ EVENT_NONE,
+ EVENT_PWRBTN,
+ EVENT_IRQ,
+ EVENT_SCI,
+ EVENT_PIN
+};
+
static struct _wdt {
+ u32 event_type;
u32 support;
long last_time;
struct regmap *iomap;
struct device *dev;
} wdt;

+static char * const type_strs[] = {
+ "NONE",
+ "PWRBTN",
+ "IRQ",
+ "SCI",
+ "PIN",
+};
+
+static u32 type_regs[] = {
+ REG_RESET_EVENT_TIME,
+ REG_PWR_EVENT_TIME,
+ REG_IRQ_EVENT_TIME,
+ REG_SCI_EVENT_TIME,
+ REG_PIN_EVENT_TIME,
+};
+
/* Pointer to the eiois200_core device structure */
static struct eiois200_dev *eiois200_dev;

+/* Specify the pin triggered on pretimeout or timeout */
+static char *event_type = "NONE";
+module_param(event_type, charp, 0);
+MODULE_PARM_DESC(event_type,
+ "Watchdog timeout event type (RESET, PWRBTN, SCI, IRQ, GPIO)");
static struct watchdog_info wdinfo = {
.identity = KBUILD_MODNAME,
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
- WDIOF_MAGICCLOSE,
+ WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE,
};

static struct watchdog_device wddev = {
@@ -76,8 +117,42 @@ static int wdt_set_timeout(struct watchdog_device *dev,
unsigned int _timeout)
{
dev->timeout = _timeout;
- dev_dbg(wdt.dev, "Set timeout: %d\n", _timeout);
+ dev_info(wdt.dev, "Set timeout: %d\n", _timeout);
+
+ return 0;
+}
+
+static int wdt_set_pretimeout(struct watchdog_device *dev,
+ unsigned int _pretimeout)
+{
+ dev->pretimeout = _pretimeout;
+
+ dev_info(wdt.dev, "Set pretimeout: %d\n", _pretimeout);
+
+ return 0;
+}
+
+static int wdt_get_type(void)
+{
+ int i;
+
+ for (i = 1; i < ARRAY_SIZE(type_strs); i++)
+ if (strcasecmp(event_type, type_strs[i]) == 0) {
+ if ((wdt.support & EVENT_BIT(i)) == 0) {
+ dev_err(wdt.dev,
+ "This board doesn't support %s trigger type\n",
+ event_type);
+ return -EINVAL;
+ }
+
+ dev_info(wdt.dev, "Trigger type is %d:%s\n",
+ i, type_strs[i]);
+ wdt.event_type = i;
+
+ return 0;
+ }

+ dev_info(wdt.dev, "Event type: %s\n", type_strs[wdt.event_type]);
return 0;
}

@@ -116,33 +191,98 @@ static int set_time(u8 ctl, u32 time)

static int wdt_set_config(void)
{
- int ret;
+ int ret, type;
+ u32 event_time = 0;
u32 reset_time = 0;

+ /* event_type should never out of range */
+ if (wdt.event_type > EVENT_PIN)
+ return -EFAULT;
+
+ /* Calculate event time and reset time */
+ if (wddev.pretimeout && wddev.timeout) {
+ if (wddev.timeout < wddev.pretimeout)
+ return -EINVAL;
+
reset_time = wddev.timeout;
+ event_time = wddev.timeout - wddev.pretimeout;

+ } else if (wddev.timeout) {
+ reset_time = wdt.event_type ? 0 : wddev.timeout;
+ event_time = wdt.event_type ? wddev.timeout : 0;
+ }
+
+ /* Set reset time */
ret = set_time(REG_RESET_EVENT_TIME, reset_time);
if (ret)
return ret;

- dev_info(wdt.dev, "Config wdt reset time %d\n", reset_time);
+ /* Set every other times */
+ for (type = 1; type < ARRAY_SIZE(type_regs); type++) {
+ ret = set_time(type_regs[type],
+ wdt.event_type == type ? event_time : 0);
+ if (ret)
+ return ret;
+ }
+
+ dev_dbg(wdt.dev, "Config wdt reset time %d\n", reset_time);
+ dev_dbg(wdt.dev, "Config wdt event time %d\n", event_time);
+ dev_dbg(wdt.dev, "Config wdt event type %s\n",
+ type_strs[wdt.event_type]);

return ret;
}

static int wdt_get_config(void)
{
- int ret;
- u32 reset_time;
+ int ret, type;
+ u32 event_time, reset_time;

/* Get Reset Time */
ret = get_time(REG_RESET_EVENT_TIME, &reset_time);
if (ret)
return ret;

- dev_info(wdt.dev, "Timeout H/W default timeout: %d secs\n", reset_time);
+ dev_dbg(wdt.dev, "Timeout H/W default timeout: %d secs\n", reset_time);
+
+ /* Get every other times **/
+ for (type = 1; type < ARRAY_SIZE(type_regs); type++) {
+ if ((wdt.support & EVENT_BIT(type)) == 0)
+ continue;
+
+ ret = get_time(type_regs[type], &event_time);
+ if (ret)
+ return ret;
+
+ if (event_time == 0)
+ continue;
+
+ if (reset_time) {
+ if (reset_time < event_time)
+ continue;
+
wddev.timeout = reset_time;
+ wddev.pretimeout = reset_time - event_time;
+
+ dev_dbg(wdt.dev, "Pretimeout H/W enabled with event %s of %d secs\n",
+ type_strs[type], wddev.pretimeout);
+ } else {
+ wddev.timeout = event_time;
+ wddev.pretimeout = 0;
+ }
+
+ wdt.event_type = type;
+
+ dev_dbg(wdt.dev, "Timeout H/W enabled of %d secs\n",
+ wddev.timeout);
+ return 0;
+ }
+
+ wdt.event_type = EVENT_NONE;
+ wddev.pretimeout = reset_time ? 0 : WATCHDOG_PRETIMEOUT;
+ wddev.timeout = reset_time ? reset_time : WATCHDOG_TIMEOUT;

+ dev_dbg(wdt.dev, "Pretimeout H/W disabled");
return 0;
}

@@ -218,6 +358,7 @@ static int wdt_support(void)

return 0;
}
+
static int wdt_init(struct device *dev)
{
int ret = 0;
@@ -230,6 +371,9 @@ static int wdt_init(struct device *dev)
if (ret)
return ret;

+ ret = wdt_get_type();
+ if (ret)
+ return ret;
return ret;
}

@@ -240,6 +384,7 @@ static const struct watchdog_ops wdt_ops = {
.ping = wdt_ping,
.set_timeout = wdt_set_timeout,
.get_timeleft = wdt_get_timeleft,
+ .set_pretimeout = wdt_set_pretimeout,
};

static int wdt_probe(struct platform_device *pdev)
--
2.34.1