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