Commit 08435c2a authored by Thomas Kastner's avatar Thomas Kastner Committed by Wim Van Sebroeck
Browse files

watchdog: Add Advantech EC watchdog driver



This patch adds the 'advantech_ec_wdt' kernel module which provides
WDT support for Advantech platforms with ITE based Embedded Controller.

Signed-off-by: default avatarThomas Kastner <thomas.kastner@advantech.com>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Tested-by: default avatarThomas Kastner <thomas.kastner@advantech.com>
Link: https://lore.kernel.org/r/Y0+pl/26e3pcEUPk@EIS-S230


Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarWim Van Sebroeck <wim@linux-watchdog.org>
parent 1d8e67ec
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -1055,6 +1055,13 @@ config ADVANTECH_WDT
	  feature. More information can be found at
	  <https://www.advantech.com.tw/products/>

config ADVANTECH_EC_WDT
	tristate "Advantech Embedded Controller Watchdog Timer"
	depends on X86
	help
		This driver supports Advantech products with ITE based Embedded Controller.
		It does not support Advantech products with other ECs or without EC.

config ALIM1535_WDT
	tristate "ALi M1535 PMU Watchdog Timer"
	depends on X86 && PCI
+1 −0
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o
# X86 (i386 + ia64 + x86_64) Architecture
obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
obj-$(CONFIG_ADVANTECH_EC_WDT) += advantech_ec_wdt.o
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
+205 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 *	Advantech Embedded Controller Watchdog Driver
 *
 *	This driver supports Advantech products with ITE based Embedded Controller.
 *	It does not support Advantech products with other ECs or without EC.
 *
 *	Copyright (C) 2022 Advantech Europe B.V.
 */

#include <linux/delay.h>
#include <linux/io.h>
#include <linux/isa.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/watchdog.h>

#define DRIVER_NAME		"advantech_ec_wdt"

/* EC IO region */
#define EC_BASE_ADDR		0x299
#define EC_ADDR_EXTENT		2

/* EC minimum IO access delay in ms */
#define EC_MIN_DELAY		10

/* EC interface definitions */
#define EC_ADDR_CMD		(EC_BASE_ADDR + 1)
#define EC_ADDR_DATA		EC_BASE_ADDR
#define EC_CMD_EC_PROBE		0x30
#define EC_CMD_COMM		0x89
#define EC_CMD_WDT_START	0x28
#define EC_CMD_WDT_STOP		0x29
#define EC_CMD_WDT_RESET	0x2A
#define EC_DAT_EN_DLY_H		0x58
#define EC_DAT_EN_DLY_L		0x59
#define EC_DAT_RST_DLY_H	0x5E
#define EC_DAT_RST_DLY_L	0x5F
#define EC_MAGIC		0x95

/* module parameters */
#define MIN_TIME		1
#define MAX_TIME		6000 /* 100 minutes */
#define DEFAULT_TIME		60

static unsigned int timeout;
static ktime_t ec_timestamp;

module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout,
		 "Default Watchdog timer setting (" __MODULE_STRING(DEFAULT_TIME) "s). The range is from " __MODULE_STRING(MIN_TIME) " to " __MODULE_STRING(MAX_TIME) ".");

static void adv_ec_wdt_timing_gate(void)
{
	ktime_t time_cur, time_delta;

	/* ensure minimum delay between IO accesses*/
	time_cur = ktime_get();
	time_delta = ktime_to_ms(ktime_sub(time_cur, ec_timestamp));
	if (time_delta < EC_MIN_DELAY) {
		time_delta = EC_MIN_DELAY - time_delta;
		usleep_range(time_delta * 1000, (time_delta + 1) * 1000);
	}
	ec_timestamp = ktime_get();
}

static void adv_ec_wdt_outb(unsigned char value, unsigned short port)
{
	adv_ec_wdt_timing_gate();
	outb(value, port);
}

static unsigned char adv_ec_wdt_inb(unsigned short port)
{
	adv_ec_wdt_timing_gate();
	return inb(port);
}

static int adv_ec_wdt_ping(struct watchdog_device *wdd)
{
	adv_ec_wdt_outb(EC_CMD_WDT_RESET, EC_ADDR_CMD);
	return 0;
}

static int adv_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
{
	unsigned int val;

	/* scale time to EC 100 ms base */
	val = t * 10;

	/* reset enable delay, just in case it was set by BIOS etc. */
	adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
	adv_ec_wdt_outb(EC_DAT_EN_DLY_H, EC_ADDR_DATA);
	adv_ec_wdt_outb(0, EC_ADDR_DATA);

	adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
	adv_ec_wdt_outb(EC_DAT_EN_DLY_L, EC_ADDR_DATA);
	adv_ec_wdt_outb(0, EC_ADDR_DATA);

	/* set reset delay */
	adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
	adv_ec_wdt_outb(EC_DAT_RST_DLY_H, EC_ADDR_DATA);
	adv_ec_wdt_outb(val >> 8, EC_ADDR_DATA);

	adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
	adv_ec_wdt_outb(EC_DAT_RST_DLY_L, EC_ADDR_DATA);
	adv_ec_wdt_outb(val & 0xFF, EC_ADDR_DATA);

	wdd->timeout = t;
	return 0;
}

static int adv_ec_wdt_start(struct watchdog_device *wdd)
{
	adv_ec_wdt_set_timeout(wdd, wdd->timeout);
	adv_ec_wdt_outb(EC_CMD_WDT_START, EC_ADDR_CMD);

	return 0;
}

static int adv_ec_wdt_stop(struct watchdog_device *wdd)
{
	adv_ec_wdt_outb(EC_CMD_WDT_STOP, EC_ADDR_CMD);

	return 0;
}

static const struct watchdog_info adv_ec_wdt_info = {
	.identity =	DRIVER_NAME,
	.options =	WDIOF_SETTIMEOUT |
			WDIOF_MAGICCLOSE |
			WDIOF_KEEPALIVEPING,
};

static const struct watchdog_ops adv_ec_wdt_ops = {
	.owner =	THIS_MODULE,
	.start =	adv_ec_wdt_start,
	.stop =		adv_ec_wdt_stop,
	.ping =		adv_ec_wdt_ping,
	.set_timeout =	adv_ec_wdt_set_timeout,
};

static struct watchdog_device adv_ec_wdt_dev = {
	.info =		&adv_ec_wdt_info,
	.ops =		&adv_ec_wdt_ops,
	.min_timeout =	MIN_TIME,
	.max_timeout =	MAX_TIME,
	.timeout =	DEFAULT_TIME,
};

static int adv_ec_wdt_probe(struct device *dev, unsigned int id)
{
	if (!devm_request_region(dev, EC_BASE_ADDR, EC_ADDR_EXTENT, dev_name(dev))) {
		dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
			EC_BASE_ADDR, EC_BASE_ADDR + EC_ADDR_EXTENT);
		return -EBUSY;
	}

	watchdog_init_timeout(&adv_ec_wdt_dev, timeout, dev);
	watchdog_stop_on_reboot(&adv_ec_wdt_dev);
	watchdog_stop_on_unregister(&adv_ec_wdt_dev);

	return devm_watchdog_register_device(dev, &adv_ec_wdt_dev);
}

static struct isa_driver adv_ec_wdt_driver = {
	.probe		= adv_ec_wdt_probe,
	.driver		= {
	.name		= DRIVER_NAME,
	},
};

static int __init adv_ec_wdt_init(void)
{
	unsigned int val;

	/* quick probe for EC */
	if (!request_region(EC_BASE_ADDR, EC_ADDR_EXTENT, DRIVER_NAME))
		return -EBUSY;

	adv_ec_wdt_outb(EC_CMD_EC_PROBE, EC_ADDR_CMD);
	val = adv_ec_wdt_inb(EC_ADDR_DATA);
	release_region(EC_BASE_ADDR, EC_ADDR_EXTENT);

	if (val != EC_MAGIC)
		return -ENODEV;

	return isa_register_driver(&adv_ec_wdt_driver, 1);
}

static void __exit adv_ec_wdt_exit(void)
{
	isa_unregister_driver(&adv_ec_wdt_driver);
}

module_init(adv_ec_wdt_init);
module_exit(adv_ec_wdt_exit);

MODULE_AUTHOR("Thomas Kastner <thomas.kastner@advantech.com>");
MODULE_DESCRIPTION("Advantech Embedded Controller Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("20221019");
MODULE_ALIAS("isa:" DRIVER_NAME);