Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Thomas Kastner and committed by
Wim Van Sebroeck
08435c2a 1d8e67ec

+213
+7
drivers/watchdog/Kconfig
··· 1055 1055 feature. More information can be found at 1056 1056 <https://www.advantech.com.tw/products/> 1057 1057 1058 + config ADVANTECH_EC_WDT 1059 + tristate "Advantech Embedded Controller Watchdog Timer" 1060 + depends on X86 1061 + help 1062 + This driver supports Advantech products with ITE based Embedded Controller. 1063 + It does not support Advantech products with other ECs or without EC. 1064 + 1058 1065 config ALIM1535_WDT 1059 1066 tristate "ALi M1535 PMU Watchdog Timer" 1060 1067 depends on X86 && PCI
+1
drivers/watchdog/Makefile
··· 102 102 # X86 (i386 + ia64 + x86_64) Architecture 103 103 obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o 104 104 obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o 105 + obj-$(CONFIG_ADVANTECH_EC_WDT) += advantech_ec_wdt.o 105 106 obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o 106 107 obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o 107 108 obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
+205
drivers/watchdog/advantech_ec_wdt.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Advantech Embedded Controller Watchdog Driver 4 + * 5 + * This driver supports Advantech products with ITE based Embedded Controller. 6 + * It does not support Advantech products with other ECs or without EC. 7 + * 8 + * Copyright (C) 2022 Advantech Europe B.V. 9 + */ 10 + 11 + #include <linux/delay.h> 12 + #include <linux/io.h> 13 + #include <linux/isa.h> 14 + #include <linux/kernel.h> 15 + #include <linux/module.h> 16 + #include <linux/moduleparam.h> 17 + #include <linux/watchdog.h> 18 + 19 + #define DRIVER_NAME "advantech_ec_wdt" 20 + 21 + /* EC IO region */ 22 + #define EC_BASE_ADDR 0x299 23 + #define EC_ADDR_EXTENT 2 24 + 25 + /* EC minimum IO access delay in ms */ 26 + #define EC_MIN_DELAY 10 27 + 28 + /* EC interface definitions */ 29 + #define EC_ADDR_CMD (EC_BASE_ADDR + 1) 30 + #define EC_ADDR_DATA EC_BASE_ADDR 31 + #define EC_CMD_EC_PROBE 0x30 32 + #define EC_CMD_COMM 0x89 33 + #define EC_CMD_WDT_START 0x28 34 + #define EC_CMD_WDT_STOP 0x29 35 + #define EC_CMD_WDT_RESET 0x2A 36 + #define EC_DAT_EN_DLY_H 0x58 37 + #define EC_DAT_EN_DLY_L 0x59 38 + #define EC_DAT_RST_DLY_H 0x5E 39 + #define EC_DAT_RST_DLY_L 0x5F 40 + #define EC_MAGIC 0x95 41 + 42 + /* module parameters */ 43 + #define MIN_TIME 1 44 + #define MAX_TIME 6000 /* 100 minutes */ 45 + #define DEFAULT_TIME 60 46 + 47 + static unsigned int timeout; 48 + static ktime_t ec_timestamp; 49 + 50 + module_param(timeout, uint, 0); 51 + MODULE_PARM_DESC(timeout, 52 + "Default Watchdog timer setting (" __MODULE_STRING(DEFAULT_TIME) "s). The range is from " __MODULE_STRING(MIN_TIME) " to " __MODULE_STRING(MAX_TIME) "."); 53 + 54 + static void adv_ec_wdt_timing_gate(void) 55 + { 56 + ktime_t time_cur, time_delta; 57 + 58 + /* ensure minimum delay between IO accesses*/ 59 + time_cur = ktime_get(); 60 + time_delta = ktime_to_ms(ktime_sub(time_cur, ec_timestamp)); 61 + if (time_delta < EC_MIN_DELAY) { 62 + time_delta = EC_MIN_DELAY - time_delta; 63 + usleep_range(time_delta * 1000, (time_delta + 1) * 1000); 64 + } 65 + ec_timestamp = ktime_get(); 66 + } 67 + 68 + static void adv_ec_wdt_outb(unsigned char value, unsigned short port) 69 + { 70 + adv_ec_wdt_timing_gate(); 71 + outb(value, port); 72 + } 73 + 74 + static unsigned char adv_ec_wdt_inb(unsigned short port) 75 + { 76 + adv_ec_wdt_timing_gate(); 77 + return inb(port); 78 + } 79 + 80 + static int adv_ec_wdt_ping(struct watchdog_device *wdd) 81 + { 82 + adv_ec_wdt_outb(EC_CMD_WDT_RESET, EC_ADDR_CMD); 83 + return 0; 84 + } 85 + 86 + static int adv_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 87 + { 88 + unsigned int val; 89 + 90 + /* scale time to EC 100 ms base */ 91 + val = t * 10; 92 + 93 + /* reset enable delay, just in case it was set by BIOS etc. */ 94 + adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD); 95 + adv_ec_wdt_outb(EC_DAT_EN_DLY_H, EC_ADDR_DATA); 96 + adv_ec_wdt_outb(0, EC_ADDR_DATA); 97 + 98 + adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD); 99 + adv_ec_wdt_outb(EC_DAT_EN_DLY_L, EC_ADDR_DATA); 100 + adv_ec_wdt_outb(0, EC_ADDR_DATA); 101 + 102 + /* set reset delay */ 103 + adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD); 104 + adv_ec_wdt_outb(EC_DAT_RST_DLY_H, EC_ADDR_DATA); 105 + adv_ec_wdt_outb(val >> 8, EC_ADDR_DATA); 106 + 107 + adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD); 108 + adv_ec_wdt_outb(EC_DAT_RST_DLY_L, EC_ADDR_DATA); 109 + adv_ec_wdt_outb(val & 0xFF, EC_ADDR_DATA); 110 + 111 + wdd->timeout = t; 112 + return 0; 113 + } 114 + 115 + static int adv_ec_wdt_start(struct watchdog_device *wdd) 116 + { 117 + adv_ec_wdt_set_timeout(wdd, wdd->timeout); 118 + adv_ec_wdt_outb(EC_CMD_WDT_START, EC_ADDR_CMD); 119 + 120 + return 0; 121 + } 122 + 123 + static int adv_ec_wdt_stop(struct watchdog_device *wdd) 124 + { 125 + adv_ec_wdt_outb(EC_CMD_WDT_STOP, EC_ADDR_CMD); 126 + 127 + return 0; 128 + } 129 + 130 + static const struct watchdog_info adv_ec_wdt_info = { 131 + .identity = DRIVER_NAME, 132 + .options = WDIOF_SETTIMEOUT | 133 + WDIOF_MAGICCLOSE | 134 + WDIOF_KEEPALIVEPING, 135 + }; 136 + 137 + static const struct watchdog_ops adv_ec_wdt_ops = { 138 + .owner = THIS_MODULE, 139 + .start = adv_ec_wdt_start, 140 + .stop = adv_ec_wdt_stop, 141 + .ping = adv_ec_wdt_ping, 142 + .set_timeout = adv_ec_wdt_set_timeout, 143 + }; 144 + 145 + static struct watchdog_device adv_ec_wdt_dev = { 146 + .info = &adv_ec_wdt_info, 147 + .ops = &adv_ec_wdt_ops, 148 + .min_timeout = MIN_TIME, 149 + .max_timeout = MAX_TIME, 150 + .timeout = DEFAULT_TIME, 151 + }; 152 + 153 + static int adv_ec_wdt_probe(struct device *dev, unsigned int id) 154 + { 155 + if (!devm_request_region(dev, EC_BASE_ADDR, EC_ADDR_EXTENT, dev_name(dev))) { 156 + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", 157 + EC_BASE_ADDR, EC_BASE_ADDR + EC_ADDR_EXTENT); 158 + return -EBUSY; 159 + } 160 + 161 + watchdog_init_timeout(&adv_ec_wdt_dev, timeout, dev); 162 + watchdog_stop_on_reboot(&adv_ec_wdt_dev); 163 + watchdog_stop_on_unregister(&adv_ec_wdt_dev); 164 + 165 + return devm_watchdog_register_device(dev, &adv_ec_wdt_dev); 166 + } 167 + 168 + static struct isa_driver adv_ec_wdt_driver = { 169 + .probe = adv_ec_wdt_probe, 170 + .driver = { 171 + .name = DRIVER_NAME, 172 + }, 173 + }; 174 + 175 + static int __init adv_ec_wdt_init(void) 176 + { 177 + unsigned int val; 178 + 179 + /* quick probe for EC */ 180 + if (!request_region(EC_BASE_ADDR, EC_ADDR_EXTENT, DRIVER_NAME)) 181 + return -EBUSY; 182 + 183 + adv_ec_wdt_outb(EC_CMD_EC_PROBE, EC_ADDR_CMD); 184 + val = adv_ec_wdt_inb(EC_ADDR_DATA); 185 + release_region(EC_BASE_ADDR, EC_ADDR_EXTENT); 186 + 187 + if (val != EC_MAGIC) 188 + return -ENODEV; 189 + 190 + return isa_register_driver(&adv_ec_wdt_driver, 1); 191 + } 192 + 193 + static void __exit adv_ec_wdt_exit(void) 194 + { 195 + isa_unregister_driver(&adv_ec_wdt_driver); 196 + } 197 + 198 + module_init(adv_ec_wdt_init); 199 + module_exit(adv_ec_wdt_exit); 200 + 201 + MODULE_AUTHOR("Thomas Kastner <thomas.kastner@advantech.com>"); 202 + MODULE_DESCRIPTION("Advantech Embedded Controller Watchdog Device Driver"); 203 + MODULE_LICENSE("GPL"); 204 + MODULE_VERSION("20221019"); 205 + MODULE_ALIAS("isa:" DRIVER_NAME);