blob: 82da9bad66ec79519a88507bab9b356b995b3417 [file] [log] [blame]
Thomas Gleixner1a59d1b82019-05-27 08:55:05 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +02002/***************************************************************************
3 * Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> *
4 * Copyright (C) 2007-2009 Hans de Goede <hdegoede@redhat.com> *
5 * Copyright (C) 2010 Giel van Schijndel <me@mortis.eu> *
6 * *
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +02007 ***************************************************************************/
8
Joe Perches27c766a2012-02-15 15:06:19 -08009#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020011#include <linux/err.h>
12#include <linux/fs.h>
13#include <linux/init.h>
14#include <linux/io.h>
15#include <linux/ioport.h>
16#include <linux/miscdevice.h>
17#include <linux/module.h>
18#include <linux/mutex.h>
19#include <linux/notifier.h>
20#include <linux/reboot.h>
21#include <linux/uaccess.h>
22#include <linux/watchdog.h>
23
24#define DRVNAME "f71808e_wdt"
25
26#define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */
27#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
Knud Poulsen85c130a2016-04-25 17:34:47 +020028#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020029
30#define SIO_REG_LDSEL 0x07 /* Logical device select */
31#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
32#define SIO_REG_DEVREV 0x22 /* Device revision */
33#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
Jaret Cantuca2fc5e2019-09-12 13:55:50 -040034#define SIO_REG_CLOCK_SEL 0x26 /* Clock select */
Lutz Ballaschke7977ff62010-09-26 16:25:35 +020035#define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +080036#define SIO_F81866_REG_PORT_SEL 0x27 /* F81866 Multi-Function Register */
Jaret Cantuca2fc5e2019-09-12 13:55:50 -040037#define SIO_REG_TSI_LEVEL_SEL 0x28 /* TSI Level select */
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +020038#define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */
39#define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */
40#define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +080041#define SIO_F81866_REG_GPIO1 0x2c /* F81866 GPIO1 Enable Register */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020042#define SIO_REG_ENABLE 0x30 /* Logical device enable */
43#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
44
45#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +020046#define SIO_F71808_ID 0x0901 /* Chipset ID */
47#define SIO_F71858_ID 0x0507 /* Chipset ID */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020048#define SIO_F71862_ID 0x0601 /* Chipset ID */
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +020049#define SIO_F71868_ID 0x1106 /* Chipset ID */
Michel Arboidf278da2010-12-06 20:53:45 +010050#define SIO_F71869_ID 0x0814 /* Chipset ID */
Justin Wheeler30170202012-06-11 01:07:58 -040051#define SIO_F71869A_ID 0x1007 /* Chipset ID */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020052#define SIO_F71882_ID 0x0541 /* Chipset ID */
53#define SIO_F71889_ID 0x0723 /* Chipset ID */
Jaret Cantuca2fc5e2019-09-12 13:55:50 -040054#define SIO_F81803_ID 0x1210 /* Chipset ID */
Knud Poulsenea0c03e2016-04-25 12:28:51 +020055#define SIO_F81865_ID 0x0704 /* Chipset ID */
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +080056#define SIO_F81866_ID 0x1010 /* Chipset ID */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020057
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020058#define F71808FG_REG_WDO_CONF 0xf0
59#define F71808FG_REG_WDT_CONF 0xf5
60#define F71808FG_REG_WD_TIME 0xf6
61
62#define F71808FG_FLAG_WDOUT_EN 7
63
Knud Poulsenb97cb212016-04-26 08:44:16 +020064#define F71808FG_FLAG_WDTMOUT_STS 6
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020065#define F71808FG_FLAG_WD_EN 5
66#define F71808FG_FLAG_WD_PULSE 4
67#define F71808FG_FLAG_WD_UNIT 3
68
Knud Poulsenea0c03e2016-04-25 12:28:51 +020069#define F81865_REG_WDO_CONF 0xfa
70#define F81865_FLAG_WDOUT_EN 0
71
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020072/* Default values */
73#define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */
74#define WATCHDOG_MAX_TIMEOUT (60 * 255)
75#define WATCHDOG_PULSE_WIDTH 125 /* 125 ms, default pulse width for
76 watchdog signal */
Lutz Ballaschke7977ff62010-09-26 16:25:35 +020077#define WATCHDOG_F71862FG_PIN 63 /* default watchdog reset output
78 pin number 63 */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020079
80static unsigned short force_id;
81module_param(force_id, ushort, 0);
82MODULE_PARM_DESC(force_id, "Override the detected device ID");
83
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +020084static int timeout = WATCHDOG_TIMEOUT; /* default timeout in seconds */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020085module_param(timeout, int, 0);
86MODULE_PARM_DESC(timeout,
87 "Watchdog timeout in seconds. 1<= timeout <="
88 __MODULE_STRING(WATCHDOG_MAX_TIMEOUT) " (default="
89 __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
90
91static unsigned int pulse_width = WATCHDOG_PULSE_WIDTH;
92module_param(pulse_width, uint, 0);
93MODULE_PARM_DESC(pulse_width,
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +020094 "Watchdog signal pulse width. 0(=level), 1, 25, 30, 125, 150, 5000 or 6000 ms"
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +020095 " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")");
96
Lutz Ballaschke7977ff62010-09-26 16:25:35 +020097static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN;
98module_param(f71862fg_pin, uint, 0);
99MODULE_PARM_DESC(f71862fg_pin,
100 "Watchdog f71862fg reset output pin configuration. Choose pin 56 or 63"
101 " (default=" __MODULE_STRING(WATCHDOG_F71862FG_PIN)")");
102
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030103static bool nowayout = WATCHDOG_NOWAYOUT;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200104module_param(nowayout, bool, 0444);
105MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
106
107static unsigned int start_withtimeout;
108module_param(start_withtimeout, uint, 0);
109MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with"
110 " given initial timeout. Zero (default) disables this feature.");
111
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200112enum chips { f71808fg, f71858fg, f71862fg, f71868, f71869, f71882fg, f71889fg,
Jaret Cantuca2fc5e2019-09-12 13:55:50 -0400113 f81803, f81865, f81866};
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200114
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200115static const char * const fintek_wdt_names[] = {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200116 "f71808fg",
117 "f71858fg",
118 "f71862fg",
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200119 "f71868",
Michel Arboidf278da2010-12-06 20:53:45 +0100120 "f71869",
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200121 "f71882fg",
122 "f71889fg",
Jaret Cantuca2fc5e2019-09-12 13:55:50 -0400123 "f81803",
Knud Poulsenea0c03e2016-04-25 12:28:51 +0200124 "f81865",
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800125 "f81866",
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200126};
127
128/* Super-I/O Function prototypes */
129static inline int superio_inb(int base, int reg);
130static inline int superio_inw(int base, int reg);
131static inline void superio_outb(int base, int reg, u8 val);
132static inline void superio_set_bit(int base, int reg, int bit);
133static inline void superio_clear_bit(int base, int reg, int bit);
134static inline int superio_enter(int base);
135static inline void superio_select(int base, int ld);
136static inline void superio_exit(int base);
137
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200138struct fintek_wdt {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200139 unsigned short sioaddr;
140 enum chips type;
141 unsigned long opened;
142 struct mutex lock;
143 char expect_close;
144 struct watchdog_info ident;
145
146 unsigned short timeout;
147 u8 timer_val; /* content for the wd_time register */
148 char minutes_mode;
149 u8 pulse_val; /* pulse width flag */
150 char pulse_mode; /* enable pulse output mode? */
151 char caused_reboot; /* last reboot was by the watchdog */
152};
153
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200154static struct fintek_wdt watchdog = {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200155 .lock = __MUTEX_INITIALIZER(watchdog.lock),
156};
157
158/* Super I/O functions */
159static inline int superio_inb(int base, int reg)
160{
161 outb(reg, base);
162 return inb(base + 1);
163}
164
165static int superio_inw(int base, int reg)
166{
167 int val;
168 val = superio_inb(base, reg) << 8;
169 val |= superio_inb(base, reg + 1);
170 return val;
171}
172
173static inline void superio_outb(int base, int reg, u8 val)
174{
175 outb(reg, base);
176 outb(val, base + 1);
177}
178
179static inline void superio_set_bit(int base, int reg, int bit)
180{
181 unsigned long val = superio_inb(base, reg);
182 __set_bit(bit, &val);
183 superio_outb(base, reg, val);
184}
185
186static inline void superio_clear_bit(int base, int reg, int bit)
187{
188 unsigned long val = superio_inb(base, reg);
189 __clear_bit(bit, &val);
190 superio_outb(base, reg, val);
191}
192
193static inline int superio_enter(int base)
194{
195 /* Don't step on other drivers' I/O space by accident */
196 if (!request_muxed_region(base, 2, DRVNAME)) {
Joe Perches27c766a2012-02-15 15:06:19 -0800197 pr_err("I/O address 0x%04x already in use\n", (int)base);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200198 return -EBUSY;
199 }
200
Justin Wheeler30170202012-06-11 01:07:58 -0400201 /* according to the datasheet the key must be sent twice! */
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200202 outb(SIO_UNLOCK_KEY, base);
203 outb(SIO_UNLOCK_KEY, base);
204
205 return 0;
206}
207
208static inline void superio_select(int base, int ld)
209{
210 outb(SIO_REG_LDSEL, base);
211 outb(ld, base + 1);
212}
213
214static inline void superio_exit(int base)
215{
216 outb(SIO_LOCK_KEY, base);
217 release_region(base, 2);
218}
219
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200220static int fintek_wdt_set_timeout(int timeout)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200221{
222 if (timeout <= 0
Ahmad Fatoumbba6c472021-08-09 18:20:32 +0200223 || timeout > WATCHDOG_MAX_TIMEOUT) {
Joe Perches27c766a2012-02-15 15:06:19 -0800224 pr_err("watchdog timeout out of range\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200225 return -EINVAL;
226 }
227
228 mutex_lock(&watchdog.lock);
229
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200230 if (timeout > 0xff) {
231 watchdog.timer_val = DIV_ROUND_UP(timeout, 60);
232 watchdog.minutes_mode = true;
Ahmad Fatoum164483c2021-08-09 18:20:31 +0200233 timeout = watchdog.timer_val * 60;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200234 } else {
235 watchdog.timer_val = timeout;
236 watchdog.minutes_mode = false;
237 }
238
Ahmad Fatoum164483c2021-08-09 18:20:31 +0200239 watchdog.timeout = timeout;
240
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200241 mutex_unlock(&watchdog.lock);
242
243 return 0;
244}
245
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200246static int fintek_wdt_set_pulse_width(unsigned int pw)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200247{
248 int err = 0;
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200249 unsigned int t1 = 25, t2 = 125, t3 = 5000;
250
251 if (watchdog.type == f71868) {
252 t1 = 30;
253 t2 = 150;
254 t3 = 6000;
255 }
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200256
257 mutex_lock(&watchdog.lock);
258
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200259 if (pw <= 1) {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200260 watchdog.pulse_val = 0;
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200261 } else if (pw <= t1) {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200262 watchdog.pulse_val = 1;
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200263 } else if (pw <= t2) {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200264 watchdog.pulse_val = 2;
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200265 } else if (pw <= t3) {
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200266 watchdog.pulse_val = 3;
267 } else {
Joe Perches27c766a2012-02-15 15:06:19 -0800268 pr_err("pulse width out of range\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200269 err = -EINVAL;
270 goto exit_unlock;
271 }
272
273 watchdog.pulse_mode = pw;
274
275exit_unlock:
276 mutex_unlock(&watchdog.lock);
277 return err;
278}
279
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200280static int fintek_wdt_keepalive(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200281{
282 int err = 0;
283
284 mutex_lock(&watchdog.lock);
285 err = superio_enter(watchdog.sioaddr);
286 if (err)
287 goto exit_unlock;
288 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
289
290 if (watchdog.minutes_mode)
291 /* select minutes for timer units */
292 superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
293 F71808FG_FLAG_WD_UNIT);
294 else
295 /* select seconds for timer units */
296 superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
297 F71808FG_FLAG_WD_UNIT);
298
299 /* Set timer value */
300 superio_outb(watchdog.sioaddr, F71808FG_REG_WD_TIME,
301 watchdog.timer_val);
302
303 superio_exit(watchdog.sioaddr);
304
305exit_unlock:
306 mutex_unlock(&watchdog.lock);
307 return err;
308}
309
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200310static int fintek_wdt_start(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200311{
Ji-Ze Hong (Peter Hong)a3f764d2019-03-27 14:42:50 +0800312 int err;
Ji-Ze Hong (Peter Hong)e347afa2019-03-27 14:42:51 +0800313 u8 tmp;
Ji-Ze Hong (Peter Hong)a3f764d2019-03-27 14:42:50 +0800314
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200315 /* Make sure we don't die as soon as the watchdog is enabled below */
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200316 err = fintek_wdt_keepalive();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200317 if (err)
318 return err;
319
320 mutex_lock(&watchdog.lock);
321 err = superio_enter(watchdog.sioaddr);
322 if (err)
323 goto exit_unlock;
324 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
325
326 /* Watchdog pin configuration */
327 switch (watchdog.type) {
328 case f71808fg:
329 /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +0200330 superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT2, 3);
331 superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 3);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200332 break;
333
Lutz Ballaschke7977ff62010-09-26 16:25:35 +0200334 case f71862fg:
Ahmad Fatoum5edc8c62020-06-11 21:17:46 +0200335 if (f71862fg_pin == 63) {
336 /* SPI must be disabled first to use this pin! */
337 superio_clear_bit(watchdog.sioaddr, SIO_REG_ROM_ADDR_SEL, 6);
338 superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 4);
339 } else if (f71862fg_pin == 56) {
340 superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1);
341 }
Lutz Ballaschke7977ff62010-09-26 16:25:35 +0200342 break;
343
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200344 case f71868:
Michel Arboidf278da2010-12-06 20:53:45 +0100345 case f71869:
346 /* GPIO14 --> WDTRST# */
347 superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 4);
348 break;
349
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200350 case f71882fg:
351 /* Set pin 56 to WDTRST# */
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +0200352 superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200353 break;
354
Giel van Schijndeldee00ab2010-10-04 10:45:28 +0200355 case f71889fg:
356 /* set pin 40 to WDTRST# */
Lutz Ballaschkef9a9f092010-09-26 16:38:20 +0200357 superio_outb(watchdog.sioaddr, SIO_REG_MFUNCT3,
358 superio_inb(watchdog.sioaddr, SIO_REG_MFUNCT3) & 0xcf);
Giel van Schijndeldee00ab2010-10-04 10:45:28 +0200359 break;
360
Jaret Cantuca2fc5e2019-09-12 13:55:50 -0400361 case f81803:
362 /* Enable TSI Level register bank */
363 superio_clear_bit(watchdog.sioaddr, SIO_REG_CLOCK_SEL, 3);
364 /* Set pin 27 to WDTRST# */
365 superio_outb(watchdog.sioaddr, SIO_REG_TSI_LEVEL_SEL, 0x5f &
366 superio_inb(watchdog.sioaddr, SIO_REG_TSI_LEVEL_SEL));
367 break;
368
Knud Poulsenea0c03e2016-04-25 12:28:51 +0200369 case f81865:
370 /* Set pin 70 to WDTRST# */
371 superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 5);
372 break;
373
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800374 case f81866:
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800375 /*
376 * GPIO1 Control Register when 27h BIT3:2 = 01 & BIT0 = 0.
377 * The PIN 70(GPIO15/WDTRST) is controlled by 2Ch:
378 * BIT5: 0 -> WDTRST#
379 * 1 -> GPIO15
380 */
Ji-Ze Hong (Peter Hong)e347afa2019-03-27 14:42:51 +0800381 tmp = superio_inb(watchdog.sioaddr, SIO_F81866_REG_PORT_SEL);
382 tmp &= ~(BIT(3) | BIT(0));
383 tmp |= BIT(2);
384 superio_outb(watchdog.sioaddr, SIO_F81866_REG_PORT_SEL, tmp);
385
386 superio_clear_bit(watchdog.sioaddr, SIO_F81866_REG_GPIO1, 5);
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800387 break;
388
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200389 default:
390 /*
391 * 'default' label to shut up the compiler and catch
392 * programmer errors
393 */
394 err = -ENODEV;
395 goto exit_superio;
396 }
397
398 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
399 superio_set_bit(watchdog.sioaddr, SIO_REG_ENABLE, 0);
Knud Poulsenea0c03e2016-04-25 12:28:51 +0200400
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800401 if (watchdog.type == f81865 || watchdog.type == f81866)
Knud Poulsenea0c03e2016-04-25 12:28:51 +0200402 superio_set_bit(watchdog.sioaddr, F81865_REG_WDO_CONF,
403 F81865_FLAG_WDOUT_EN);
404 else
405 superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDO_CONF,
406 F71808FG_FLAG_WDOUT_EN);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200407
408 superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
409 F71808FG_FLAG_WD_EN);
410
411 if (watchdog.pulse_mode) {
412 /* Select "pulse" output mode with given duration */
413 u8 wdt_conf = superio_inb(watchdog.sioaddr,
414 F71808FG_REG_WDT_CONF);
415
416 /* Set WD_PSWIDTH bits (1:0) */
417 wdt_conf = (wdt_conf & 0xfc) | (watchdog.pulse_val & 0x03);
418 /* Set WD_PULSE to "pulse" mode */
419 wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE);
420
421 superio_outb(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
422 wdt_conf);
423 } else {
424 /* Select "level" output mode */
425 superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
426 F71808FG_FLAG_WD_PULSE);
427 }
428
429exit_superio:
430 superio_exit(watchdog.sioaddr);
431exit_unlock:
432 mutex_unlock(&watchdog.lock);
433
434 return err;
435}
436
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200437static int fintek_wdt_stop(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200438{
439 int err = 0;
440
441 mutex_lock(&watchdog.lock);
442 err = superio_enter(watchdog.sioaddr);
443 if (err)
444 goto exit_unlock;
445 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
446
447 superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
448 F71808FG_FLAG_WD_EN);
449
450 superio_exit(watchdog.sioaddr);
451
452exit_unlock:
453 mutex_unlock(&watchdog.lock);
454
455 return err;
456}
457
458static int watchdog_get_status(void)
459{
460 int status = 0;
461
462 mutex_lock(&watchdog.lock);
463 status = (watchdog.caused_reboot) ? WDIOF_CARDRESET : 0;
464 mutex_unlock(&watchdog.lock);
465
466 return status;
467}
468
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200469static bool fintek_wdt_is_running(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200470{
471 /*
472 * if we fail to determine the watchdog's status assume it to be
473 * running to be on the safe side
474 */
475 bool is_running = true;
476
477 mutex_lock(&watchdog.lock);
478 if (superio_enter(watchdog.sioaddr))
479 goto exit_unlock;
480 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
481
482 is_running = (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0))
483 && (superio_inb(watchdog.sioaddr, F71808FG_REG_WDT_CONF)
Igor Pylypiv977f6f62018-03-06 23:47:25 -0800484 & BIT(F71808FG_FLAG_WD_EN));
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200485
486 superio_exit(watchdog.sioaddr);
487
488exit_unlock:
489 mutex_unlock(&watchdog.lock);
490 return is_running;
491}
492
493/* /dev/watchdog api */
494
495static int watchdog_open(struct inode *inode, struct file *file)
496{
497 int err;
498
499 /* If the watchdog is alive we don't need to start it again */
500 if (test_and_set_bit(0, &watchdog.opened))
501 return -EBUSY;
502
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200503 err = fintek_wdt_start();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200504 if (err) {
505 clear_bit(0, &watchdog.opened);
506 return err;
507 }
508
509 if (nowayout)
510 __module_get(THIS_MODULE);
511
512 watchdog.expect_close = 0;
Kirill Smelkovc5bf68f2019-03-26 23:51:19 +0300513 return stream_open(inode, file);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200514}
515
516static int watchdog_release(struct inode *inode, struct file *file)
517{
518 clear_bit(0, &watchdog.opened);
519
520 if (!watchdog.expect_close) {
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200521 fintek_wdt_keepalive();
Joe Perches27c766a2012-02-15 15:06:19 -0800522 pr_crit("Unexpected close, not stopping watchdog!\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200523 } else if (!nowayout) {
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200524 fintek_wdt_stop();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200525 }
526 return 0;
527}
528
529/*
530 * watchdog_write:
531 * @file: file handle to the watchdog
532 * @buf: buffer to write
533 * @count: count of bytes
534 * @ppos: pointer to the position to write. No seeks allowed
535 *
536 * A write to a watchdog device is defined as a keepalive signal. Any
537 * write of data will do, as we we don't define content meaning.
538 */
539
540static ssize_t watchdog_write(struct file *file, const char __user *buf,
541 size_t count, loff_t *ppos)
542{
543 if (count) {
544 if (!nowayout) {
545 size_t i;
546
547 /* In case it was set long ago */
548 bool expect_close = false;
549
550 for (i = 0; i != count; i++) {
551 char c;
552 if (get_user(c, buf + i))
553 return -EFAULT;
Igor Pylypiv7bd3e7b2018-02-28 00:59:12 -0800554 if (c == 'V')
555 expect_close = true;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200556 }
557
558 /* Properly order writes across fork()ed processes */
559 mutex_lock(&watchdog.lock);
560 watchdog.expect_close = expect_close;
561 mutex_unlock(&watchdog.lock);
562 }
563
564 /* someone wrote to us, we should restart timer */
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200565 fintek_wdt_keepalive();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200566 }
567 return count;
568}
569
570/*
571 * watchdog_ioctl:
572 * @inode: inode of the device
573 * @file: file handle to the device
574 * @cmd: watchdog command
575 * @arg: argument pointer
576 *
577 * The watchdog API defines a common set of functions for all watchdogs
578 * according to their available features.
579 */
580static long watchdog_ioctl(struct file *file, unsigned int cmd,
581 unsigned long arg)
582{
583 int status;
584 int new_options;
585 int new_timeout;
586 union {
587 struct watchdog_info __user *ident;
588 int __user *i;
589 } uarg;
590
591 uarg.i = (int __user *)arg;
592
593 switch (cmd) {
594 case WDIOC_GETSUPPORT:
595 return copy_to_user(uarg.ident, &watchdog.ident,
596 sizeof(watchdog.ident)) ? -EFAULT : 0;
597
598 case WDIOC_GETSTATUS:
599 status = watchdog_get_status();
600 if (status < 0)
601 return status;
602 return put_user(status, uarg.i);
603
604 case WDIOC_GETBOOTSTATUS:
605 return put_user(0, uarg.i);
606
607 case WDIOC_SETOPTIONS:
608 if (get_user(new_options, uarg.i))
609 return -EFAULT;
610
611 if (new_options & WDIOS_DISABLECARD)
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200612 fintek_wdt_stop();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200613
614 if (new_options & WDIOS_ENABLECARD)
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200615 return fintek_wdt_start();
Gustavo A. R. Silvabd490f82020-07-07 12:11:21 -0500616 fallthrough;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200617
618 case WDIOC_KEEPALIVE:
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200619 fintek_wdt_keepalive();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200620 return 0;
621
622 case WDIOC_SETTIMEOUT:
623 if (get_user(new_timeout, uarg.i))
624 return -EFAULT;
625
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200626 if (fintek_wdt_set_timeout(new_timeout))
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200627 return -EINVAL;
628
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200629 fintek_wdt_keepalive();
Gustavo A. R. Silvabd490f82020-07-07 12:11:21 -0500630 fallthrough;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200631
632 case WDIOC_GETTIMEOUT:
633 return put_user(watchdog.timeout, uarg.i);
634
635 default:
636 return -ENOTTY;
637
638 }
639}
640
641static int watchdog_notify_sys(struct notifier_block *this, unsigned long code,
642 void *unused)
643{
644 if (code == SYS_DOWN || code == SYS_HALT)
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200645 fintek_wdt_stop();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200646 return NOTIFY_DONE;
647}
648
649static const struct file_operations watchdog_fops = {
650 .owner = THIS_MODULE,
651 .llseek = no_llseek,
652 .open = watchdog_open,
653 .release = watchdog_release,
654 .write = watchdog_write,
655 .unlocked_ioctl = watchdog_ioctl,
Arnd Bergmannb6dfb242019-06-03 14:23:09 +0200656 .compat_ioctl = compat_ptr_ioctl,
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200657};
658
659static struct miscdevice watchdog_miscdev = {
660 .minor = WATCHDOG_MINOR,
661 .name = "watchdog",
662 .fops = &watchdog_fops,
663};
664
665static struct notifier_block watchdog_notifier = {
666 .notifier_call = watchdog_notify_sys,
667};
668
669static int __init watchdog_init(int sioaddr)
670{
671 int wdt_conf, err = 0;
672
673 /* No need to lock watchdog.lock here because no entry points
674 * into the module have been registered yet.
675 */
676 watchdog.sioaddr = sioaddr;
Ahmad Fatoum80214142020-06-11 21:17:44 +0200677 watchdog.ident.options = WDIOF_MAGICCLOSE
Ahmad Fatoume871e932020-06-11 21:17:43 +0200678 | WDIOF_KEEPALIVEPING
679 | WDIOF_CARDRESET;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200680
681 snprintf(watchdog.ident.identity,
682 sizeof(watchdog.ident.identity), "%s watchdog",
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200683 fintek_wdt_names[watchdog.type]);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200684
685 err = superio_enter(sioaddr);
686 if (err)
687 return err;
688 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
689
690 wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF);
Knud Poulsenb97cb212016-04-26 08:44:16 +0200691 watchdog.caused_reboot = wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200692
Ahmad Fatoum4f39d572020-06-11 21:17:45 +0200693 /*
694 * We don't want WDTMOUT_STS to stick around till regular reboot.
695 * Write 1 to the bit to clear it to zero.
696 */
697 superio_outb(sioaddr, F71808FG_REG_WDT_CONF,
698 wdt_conf | BIT(F71808FG_FLAG_WDTMOUT_STS));
699
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200700 superio_exit(sioaddr);
701
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200702 err = fintek_wdt_set_timeout(timeout);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200703 if (err)
704 return err;
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200705 err = fintek_wdt_set_pulse_width(pulse_width);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200706 if (err)
707 return err;
708
709 err = register_reboot_notifier(&watchdog_notifier);
710 if (err)
711 return err;
712
713 err = misc_register(&watchdog_miscdev);
714 if (err) {
Joe Perches27c766a2012-02-15 15:06:19 -0800715 pr_err("cannot register miscdev on minor=%d\n",
716 watchdog_miscdev.minor);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200717 goto exit_reboot;
718 }
719
720 if (start_withtimeout) {
721 if (start_withtimeout <= 0
Ahmad Fatoumbba6c472021-08-09 18:20:32 +0200722 || start_withtimeout > WATCHDOG_MAX_TIMEOUT) {
Joe Perches27c766a2012-02-15 15:06:19 -0800723 pr_err("starting timeout out of range\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200724 err = -EINVAL;
725 goto exit_miscdev;
726 }
727
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200728 err = fintek_wdt_start();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200729 if (err) {
Joe Perches27c766a2012-02-15 15:06:19 -0800730 pr_err("cannot start watchdog timer\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200731 goto exit_miscdev;
732 }
733
734 mutex_lock(&watchdog.lock);
735 err = superio_enter(sioaddr);
736 if (err)
737 goto exit_unlock;
738 superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
739
740 if (start_withtimeout > 0xff) {
741 /* select minutes for timer units */
742 superio_set_bit(sioaddr, F71808FG_REG_WDT_CONF,
743 F71808FG_FLAG_WD_UNIT);
744 superio_outb(sioaddr, F71808FG_REG_WD_TIME,
745 DIV_ROUND_UP(start_withtimeout, 60));
746 } else {
747 /* select seconds for timer units */
748 superio_clear_bit(sioaddr, F71808FG_REG_WDT_CONF,
749 F71808FG_FLAG_WD_UNIT);
750 superio_outb(sioaddr, F71808FG_REG_WD_TIME,
751 start_withtimeout);
752 }
753
754 superio_exit(sioaddr);
755 mutex_unlock(&watchdog.lock);
756
757 if (nowayout)
758 __module_get(THIS_MODULE);
759
Joe Perches27c766a2012-02-15 15:06:19 -0800760 pr_info("watchdog started with initial timeout of %u sec\n",
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200761 start_withtimeout);
762 }
763
764 return 0;
765
766exit_unlock:
767 mutex_unlock(&watchdog.lock);
768exit_miscdev:
769 misc_deregister(&watchdog_miscdev);
770exit_reboot:
771 unregister_reboot_notifier(&watchdog_notifier);
772
773 return err;
774}
775
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200776static int __init fintek_wdt_find(int sioaddr)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200777{
778 u16 devid;
779 int err = superio_enter(sioaddr);
780 if (err)
781 return err;
782
783 devid = superio_inw(sioaddr, SIO_REG_MANID);
784 if (devid != SIO_FINTEK_ID) {
Joe Perches27c766a2012-02-15 15:06:19 -0800785 pr_debug("Not a Fintek device\n");
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200786 err = -ENODEV;
787 goto exit;
788 }
789
790 devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID);
791 switch (devid) {
792 case SIO_F71808_ID:
793 watchdog.type = f71808fg;
794 break;
Lutz Ballaschke7977ff62010-09-26 16:25:35 +0200795 case SIO_F71862_ID:
796 watchdog.type = f71862fg;
Lutz Ballaschke7977ff62010-09-26 16:25:35 +0200797 break;
Maciej S. Szmigiero166fbcf2017-04-17 22:37:05 +0200798 case SIO_F71868_ID:
799 watchdog.type = f71868;
800 break;
Michel Arboidf278da2010-12-06 20:53:45 +0100801 case SIO_F71869_ID:
Justin Wheeler30170202012-06-11 01:07:58 -0400802 case SIO_F71869A_ID:
Michel Arboidf278da2010-12-06 20:53:45 +0100803 watchdog.type = f71869;
804 break;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200805 case SIO_F71882_ID:
806 watchdog.type = f71882fg;
807 break;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200808 case SIO_F71889_ID:
Giel van Schijndeldee00ab2010-10-04 10:45:28 +0200809 watchdog.type = f71889fg;
810 break;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200811 case SIO_F71858_ID:
812 /* Confirmed (by datasheet) not to have a watchdog. */
813 err = -ENODEV;
814 goto exit;
Jaret Cantuca2fc5e2019-09-12 13:55:50 -0400815 case SIO_F81803_ID:
816 watchdog.type = f81803;
817 break;
Knud Poulsenea0c03e2016-04-25 12:28:51 +0200818 case SIO_F81865_ID:
819 watchdog.type = f81865;
820 break;
Ji-Ze Hong (Peter Hong)14b24a82016-06-08 14:57:50 +0800821 case SIO_F81866_ID:
822 watchdog.type = f81866;
823 break;
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200824 default:
Joe Perches27c766a2012-02-15 15:06:19 -0800825 pr_info("Unrecognized Fintek device: %04x\n",
826 (unsigned int)devid);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200827 err = -ENODEV;
828 goto exit;
829 }
830
Joe Perches27c766a2012-02-15 15:06:19 -0800831 pr_info("Found %s watchdog chip, revision %d\n",
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200832 fintek_wdt_names[watchdog.type],
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200833 (int)superio_inb(sioaddr, SIO_REG_DEVREV));
834exit:
835 superio_exit(sioaddr);
836 return err;
837}
838
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200839static int __init fintek_wdt_init(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200840{
841 static const unsigned short addrs[] = { 0x2e, 0x4e };
842 int err = -ENODEV;
843 int i;
844
Ahmad Fatoum5edc8c62020-06-11 21:17:46 +0200845 if (f71862fg_pin != 63 && f71862fg_pin != 56) {
846 pr_err("Invalid argument f71862fg_pin=%d\n", f71862fg_pin);
847 return -EINVAL;
848 }
849
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200850 for (i = 0; i < ARRAY_SIZE(addrs); i++) {
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200851 err = fintek_wdt_find(addrs[i]);
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200852 if (err == 0)
853 break;
854 }
855 if (i == ARRAY_SIZE(addrs))
856 return err;
857
858 return watchdog_init(addrs[i]);
859}
860
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200861static void __exit fintek_wdt_exit(void)
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200862{
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200863 if (fintek_wdt_is_running()) {
Joe Perches27c766a2012-02-15 15:06:19 -0800864 pr_warn("Watchdog timer still running, stopping it\n");
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200865 fintek_wdt_stop();
Giel van Schijndel96cb4eb2010-08-01 15:30:55 +0200866 }
867 misc_deregister(&watchdog_miscdev);
868 unregister_reboot_notifier(&watchdog_notifier);
869}
870
871MODULE_DESCRIPTION("F71808E Watchdog Driver");
872MODULE_AUTHOR("Giel van Schijndel <me@mortis.eu>");
873MODULE_LICENSE("GPL");
874
Ahmad Fatoum3a2c4892021-08-09 18:20:34 +0200875module_init(fintek_wdt_init);
876module_exit(fintek_wdt_exit);