xref: /linux/drivers/watchdog/advantech_ec_wdt.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
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 
adv_ec_wdt_timing_gate(void)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 
adv_ec_wdt_outb(unsigned char value,unsigned short port)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 
adv_ec_wdt_inb(unsigned short port)74 static unsigned char adv_ec_wdt_inb(unsigned short port)
75 {
76 	adv_ec_wdt_timing_gate();
77 	return inb(port);
78 }
79 
adv_ec_wdt_ping(struct watchdog_device * wdd)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 
adv_ec_wdt_set_timeout(struct watchdog_device * wdd,unsigned int t)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 
adv_ec_wdt_start(struct watchdog_device * wdd)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 
adv_ec_wdt_stop(struct watchdog_device * wdd)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 
adv_ec_wdt_probe(struct device * dev,unsigned int id)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 
adv_ec_wdt_init(void)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 
adv_ec_wdt_exit(void)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);
206