diff mbox series

[5/5] watchdog: eiois200_wdt: Enhanced IRQ trigger behavior

Message ID 0ddce182fa353592979be2a6762e89a1dc436985.1696495372.git.advantech.susiteam@gmail.com
State New
Headers show
Series watchdog: eiois200_wdt: Add EIO-IS200 Watchdog Driver | expand

Commit Message

Wenkai Oct. 5, 2023, 8:51 a.m. UTC
From: Wenkai <advantech.susiteam@gmail.com>

This patch improved functionality when IRQ is used as the trigger event.

When the IRQ event selected. If the pretimeout is specified, when the
pretimeout time expires, it will trigger the associated IRQ event and
invoke the pretimeout governor's actions.

And, if the pretimeout is not specified, when the timeout time expires,
the driver initiates an emergency system restart.

Signed-off-by: Wenkai <advantech.susiteam@gmail.com>
---
 drivers/watchdog/eiois200_wdt.c | 237 ++++++++++++++++++++++++++++++--
 1 file changed, 228 insertions(+), 9 deletions(-)
diff mbox series

Patch

diff --git a/drivers/watchdog/eiois200_wdt.c b/drivers/watchdog/eiois200_wdt.c
index 85179806ab7e..c9acb63b1152 100644
--- a/drivers/watchdog/eiois200_wdt.c
+++ b/drivers/watchdog/eiois200_wdt.c
@@ -2,10 +2,34 @@ 
 /*
  * Advantech EIO-IS200 Watchdog Driver
  *
+ * This driver enables watchdog functionality for the Advantech EIO-IS200
+ * embedded controller. Its has a dependency on the eiois200_core module.
+ * It allows the specification of a timeout or pretimeout associated trigger
+ * event, which can be one of the following pins:
+ * - 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 always triggers
+ * a reset. If the associated pin is IRQ, the IRQ will trigger the system's
+ * original pretimeout behavior through the pretimeout governor.
+ *
+ * If the pretimeout is not specified, the timeout expiration triggers the
+ * associated pin only. If the associated pin is IRQ, it triggers a system
+ * emergency restart.
+ *
+ * NOTE: Advantech machines are shipped with proper IRQ and related event
+ * configurations. If you are unsure about these settings, just keep the
+ * device's default settings, and load this module without specifying any
+ * parameters.
+ *
  * Copyright (C) 2023 Advantech Co., Ltd.
  * Author: wenkai <advantech.susiteam@gmail.com>
  */
 
+#include <linux/interrupt.h>
 #include <linux/mfd/core.h>
 #include <linux/reboot.h>
 #include <linux/uaccess.h>
@@ -59,7 +83,7 @@ 
 #define PMC_READ(cmd, data)	pmc(CMD_WDT_READ, cmd, data)
 
 /* Mapping event type to supported bit */
-#define EVENT_BIT(type)   	BIT(type + 2)
+#define EVENT_BIT(type)	BIT(type + 2)
 
 enum event_type {
 	EVENT_NONE,
@@ -72,6 +96,7 @@  enum event_type {
 static struct _wdt {
 	u32	event_type;
 	u32	support;
+	u32	irq;
 	long	last_time;
 	struct	regmap  *iomap;
 	struct	device *dev;
@@ -101,6 +126,12 @@  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)");
+
+/* Specify the IRQ number when the IRQ event is triggered */
+static int irq;
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The IRQ number for IRQ event");
+
 static struct watchdog_info wdinfo = {
 	.identity = KBUILD_MODNAME,
 	.options  = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
@@ -145,8 +176,8 @@  static int wdt_get_type(void)
 				return -EINVAL;
 			}
 
-			dev_info(wdt.dev, "Trigger type is %d:%s\n", 
-					  i, type_strs[i]);
+			dev_info(wdt.dev, "Trigger type is %d:%s\n",
+				 i, type_strs[i]);
 			wdt.event_type = i;
 
 			return 0;
@@ -204,7 +235,7 @@  static int wdt_set_config(void)
 		if (wddev.timeout < wddev.pretimeout)
 			return -EINVAL;
 
-	reset_time = wddev.timeout;
+		reset_time = wddev.timeout;
 		event_time = wddev.timeout - wddev.pretimeout;
 
 	} else if (wddev.timeout) {
@@ -228,7 +259,7 @@  static int wdt_set_config(void)
 	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]);
+		type_strs[wdt.event_type]);
 
 	return ret;
 }
@@ -261,11 +292,11 @@  static int wdt_get_config(void)
 			if (reset_time < event_time)
 				continue;
 
-	wddev.timeout	 = reset_time;
+			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);
+				type_strs[type], wddev.pretimeout);
 		} else {
 			wddev.timeout = event_time;
 			wddev.pretimeout = 0;
@@ -274,7 +305,7 @@  static int wdt_get_config(void)
 		wdt.event_type = type;
 
 		dev_dbg(wdt.dev, "Timeout H/W enabled of %d secs\n",
-				  wddev.timeout);
+			wddev.timeout);
 		return 0;
 	}
 
@@ -359,6 +390,180 @@  static int wdt_support(void)
 	return 0;
 }
 
+static int wdt_get_irq_io(void)
+{
+	int ret  = 0;
+	int idx  = EIOIS200_PNP_INDEX;
+	int data = EIOIS200_PNP_DATA;
+	struct regmap *map = wdt.iomap;
+
+	mutex_lock(&eiois200_dev->mutex);
+
+	/* Unlock EC IO port */
+	ret |= regmap_write(map, idx,  IOREG_UNLOCK);
+	ret |= regmap_write(map, idx,  IOREG_UNLOCK);
+
+	/* Select logical device to PMC */
+	ret |= regmap_write(map, idx,  IOREG_LDN);
+	ret |= regmap_write(map, data, IOREG_LDN_PMCIO);
+
+	/* Get IRQ number */
+	ret |= regmap_write(map, idx,  IOREG_IRQ);
+	ret |= regmap_read(map, data, &wdt.irq);
+
+	/* Lock up */
+	ret |= regmap_write(map, idx,  IOREG_LOCK);
+
+	mutex_unlock(&eiois200_dev->mutex);
+
+	return ret ? -EIO : 0;
+}
+
+static int wdt_get_irq_pmc(void)
+{
+	return PMC_READ(REG_IRQ_NUMBER, &wdt.irq);
+}
+
+static int wdt_get_irq(struct device *dev)
+{
+	int ret;
+
+	if ((wdt.support & BIT(EVENT_IRQ)) == 0)
+		return -ENODEV;
+
+	/* Get IRQ number through PMC */
+	ret = wdt_get_irq_pmc();
+	if (ret)
+		return dev_err_probe(dev, ret, "Error get irq by pmc\n");
+
+	if (wdt.irq)
+		return 0;
+
+	/* Get IRQ number from the watchdog device in EC */
+	ret = wdt_get_irq_io();
+	if (ret)
+		return dev_err_probe(dev, ret, "Error get irq by io\n");
+
+	if (wdt.irq == 0)
+		return dev_err_probe(dev, ret, "Error IRQ number = 0\n");
+
+	return ret;
+}
+
+static int wdt_set_irq_io(void)
+{
+	int ret  = 0;
+	int idx  = EIOIS200_PNP_INDEX;
+	int data = EIOIS200_PNP_DATA;
+	struct regmap *map = wdt.iomap;
+
+	mutex_lock(&eiois200_dev->mutex);
+
+	/* Unlock EC IO port */
+	ret |= regmap_write(map, idx,  IOREG_UNLOCK);
+	ret |= regmap_write(map, idx,  IOREG_UNLOCK);
+
+	/* Select logical device to PMC */
+	ret |= regmap_write(map, idx,  IOREG_LDN);
+	ret |= regmap_write(map, data, IOREG_LDN_PMCIO);
+
+	/* Enable WDT */
+	ret |= regmap_write(map, idx,  IOREG_WDT_STATUS);
+	ret |= regmap_write(map, data, FLAG_WDT_ENABLED);
+
+	/* Set IRQ number */
+	ret |= regmap_write(map, idx,  IOREG_IRQ);
+	ret |= regmap_write(map, data, wdt.irq);
+
+	/* Lock up */
+	ret |= regmap_write(map, idx,  IOREG_LOCK);
+
+	mutex_unlock(&eiois200_dev->mutex);
+
+	return ret ? -EIO : 0;
+}
+
+static int wdt_set_irq_pmc(void)
+{
+	return PMC_WRITE(REG_IRQ_NUMBER, &wdt.irq);
+}
+
+static int wdt_set_irq(struct device *dev)
+{
+	int ret;
+
+	if ((wdt.support & BIT(EVENT_IRQ)) == 0)
+		return -ENODEV;
+
+	/* Set IRQ number to the watchdog device in EC */
+	ret = wdt_set_irq_io();
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Error set irq by io\n");
+
+	/* Notice EC that watchdog IRQ changed */
+	ret = wdt_set_irq_pmc();
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Error set irq by pmc\n");
+
+	return ret;
+}
+
+/**
+ * wdt_get_irq_event - Check if IRQ been triggered
+ * Returns:	The current status read from the PMC,
+ *		or 0 if there was an error.
+ */
+static int wdt_get_irq_event(void)
+{
+	u8 status;
+
+	if (PMC_READ(REG_EVENT, &status))
+		return 0;
+
+	return status;
+}
+
+static irqreturn_t wdt_isr(int irq, void *arg)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t wdt_threaded_isr(int irq, void *arg)
+{
+	u8 status = wdt_get_irq_event() & FLAG_TRIGGER_IRQ;
+
+	if (!status)
+		return IRQ_NONE;
+
+	if (wddev.pretimeout) {
+		watchdog_notify_pretimeout(&wddev);
+	} else {
+		pr_crit("Watchdog Timer expired. Initiating system reboot\n");
+		emergency_restart();
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int query_irq(struct device *dev)
+{
+	int ret;
+
+	if (irq) {
+		wdt.irq = irq;
+	} else {
+		ret = wdt_get_irq(dev);
+		if (ret)
+			return ret;
+	}
+
+	dev_dbg(wdt.dev, "IRQ = %d\n", wdt.irq);
+
+	return wdt_set_irq(dev);
+}
+
 static int wdt_init(struct device *dev)
 {
 	int ret = 0;
@@ -374,6 +579,10 @@  static int wdt_init(struct device *dev)
 	ret = wdt_get_type();
 	if (ret)
 		return ret;
+
+	if (wdt.event_type == EVENT_IRQ)
+		ret = query_irq(dev);
+
 	return ret;
 }
 
@@ -402,11 +611,21 @@  static int wdt_probe(struct platform_device *pdev)
 	wdt.iomap = dev_get_regmap(dev->parent, NULL);
 	if (!wdt.iomap)
 		return dev_err_probe(dev, -ENOMEM, "Query parent regmap fail\n");
-	
+
 	/* Initialize EC watchdog */
 	if (wdt_init(dev))
 		return dev_err_probe(dev, -EIO, "wdt_init fail\n");
 
+	/* Request IRQ */
+	if (wdt.event_type == EVENT_IRQ)
+		ret = devm_request_threaded_irq(dev, wdt.irq, wdt_isr,
+						wdt_threaded_isr,
+						IRQF_SHARED, pdev->name, dev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				    "IRQ %d request fail:%d. Disabled.\n",
+				    wdt.irq, ret);
+
 	/* Inform watchdog info */
 	wddev.ops = &wdt_ops;
 	ret = watchdog_init_timeout(&wddev, wddev.timeout, dev);