summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/advantech_ec_wdt.c
blob: 7c380f90ca5831c521755d92445b4dc083721e91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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);