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: Thomas Kastner <thomas.kastner@advantech.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Thomas Kastner <thomas.kastner@advantech.com>
Link: https://lore.kernel.org/r/Y0+pl/26e3pcEUPk@EIS-S230
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
This commit is contained in:
Thomas Kastner 2022-10-19 09:39:03 +02:00 committed by Wim Van Sebroeck
parent 1d8e67ecf1
commit 08435c2aab
3 changed files with 213 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,205 @@
// 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);