xref: /linux/drivers/watchdog/iTCO_wdt.c (revision f34c51252189e6f18f3983f7cb7cc46f2e54ffe9)
1d0173278SGuenter Roeck // SPDX-License-Identifier: GPL-2.0+
2b7e04f8cSWim Van Sebroeck /*
3cb711a19SWim Van Sebroeck  *	intel TCO Watchdog Driver
4b7e04f8cSWim Van Sebroeck  *
5deb9197bSWim Van Sebroeck  *	(c) Copyright 2006-2011 Wim Van Sebroeck <wim@iguana.be>.
6b7e04f8cSWim Van Sebroeck  *
7b7e04f8cSWim Van Sebroeck  *	Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
8b7e04f8cSWim Van Sebroeck  *	provide warranty for any of this software. This material is
9b7e04f8cSWim Van Sebroeck  *	provided "AS-IS" and at no charge.
10b7e04f8cSWim Van Sebroeck  *
11b7e04f8cSWim Van Sebroeck  *	The TCO watchdog is implemented in the following I/O controller hubs:
12b7e04f8cSWim Van Sebroeck  *	(See the intel documentation on http://developer.intel.com.)
13cb711a19SWim Van Sebroeck  *	document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO)
14cb711a19SWim Van Sebroeck  *	document number 290687-002, 298242-027: 82801BA (ICH2)
15cb711a19SWim Van Sebroeck  *	document number 290733-003, 290739-013: 82801CA (ICH3-S)
16cb711a19SWim Van Sebroeck  *	document number 290716-001, 290718-007: 82801CAM (ICH3-M)
17cb711a19SWim Van Sebroeck  *	document number 290744-001, 290745-025: 82801DB (ICH4)
18cb711a19SWim Van Sebroeck  *	document number 252337-001, 252663-008: 82801DBM (ICH4-M)
19cb711a19SWim Van Sebroeck  *	document number 273599-001, 273645-002: 82801E (C-ICH)
20cb711a19SWim Van Sebroeck  *	document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R)
21cb711a19SWim Van Sebroeck  *	document number 300641-004, 300884-013: 6300ESB
22cb711a19SWim Van Sebroeck  *	document number 301473-002, 301474-026: 82801F (ICH6)
23cb711a19SWim Van Sebroeck  *	document number 313082-001, 313075-006: 631xESB, 632xESB
24cb711a19SWim Van Sebroeck  *	document number 307013-003, 307014-024: 82801G (ICH7)
25d38bd479SWim Van Sebroeck  *	document number 322896-001, 322897-001: NM10
26cb711a19SWim Van Sebroeck  *	document number 313056-003, 313057-017: 82801H (ICH8)
27cb711a19SWim Van Sebroeck  *	document number 316972-004, 316973-012: 82801I (ICH9)
28cb711a19SWim Van Sebroeck  *	document number 319973-002, 319974-002: 82801J (ICH10)
293c9d8eccSSeth Heasley  *	document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH)
304946f835SImre Kaloz  *	document number 320066-003, 320257-008: EP80597 (IICH)
31203f8d89SSeth Heasley  *	document number 324645-001, 324646-001: Cougar Point (CPT)
32c54fb811SSeth Heasley  *	document number TBD                   : Patsburg (PBG)
33203f8d89SSeth Heasley  *	document number TBD                   : DH89xxCC
34aa1f4652SSeth Heasley  *	document number TBD                   : Panther Point
3584e83c28SSeth Heasley  *	document number TBD                   : Lynx Point
367fb9c1a4SJames Ralston  *	document number TBD                   : Lynx Point-LP
37b7e04f8cSWim Van Sebroeck  */
38b7e04f8cSWim Van Sebroeck 
39b7e04f8cSWim Van Sebroeck /*
40b7e04f8cSWim Van Sebroeck  *	Includes, defines, variables, module parameters, ...
41b7e04f8cSWim Van Sebroeck  */
42b7e04f8cSWim Van Sebroeck 
43b7e04f8cSWim Van Sebroeck /* Module and version information */
44b7e04f8cSWim Van Sebroeck #define DRV_NAME	"iTCO_wdt"
4524b3a167SPeter Tyser #define DRV_VERSION	"1.11"
46b7e04f8cSWim Van Sebroeck 
47b7e04f8cSWim Van Sebroeck /* Includes */
48f321c9cbSRafael J. Wysocki #include <linux/acpi.h>			/* For ACPI support */
49da23b6faSMika Westerberg #include <linux/bits.h>			/* For BIT() */
50b7e04f8cSWim Van Sebroeck #include <linux/module.h>		/* For module specific items */
51b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>		/* For new moduleparam's */
52b7e04f8cSWim Van Sebroeck #include <linux/types.h>		/* For standard types (like size_t) */
53b7e04f8cSWim Van Sebroeck #include <linux/errno.h>		/* For the -ENODEV/... values */
54b7e04f8cSWim Van Sebroeck #include <linux/kernel.h>		/* For printk/panic/... */
55b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>		/* For the watchdog specific items */
56b7e04f8cSWim Van Sebroeck #include <linux/init.h>			/* For __init/__exit/... */
57b7e04f8cSWim Van Sebroeck #include <linux/fs.h>			/* For file operations */
58b7e04f8cSWim Van Sebroeck #include <linux/platform_device.h>	/* For platform_driver framework */
59b7e04f8cSWim Van Sebroeck #include <linux/pci.h>			/* For pci functions */
60b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>		/* For io-port access */
61b7e04f8cSWim Van Sebroeck #include <linux/spinlock.h>		/* For spin_lock/spin_unlock/... */
620e6fa3fbSAlan Cox #include <linux/uaccess.h>		/* For copy_to_user/put_user/... */
630e6fa3fbSAlan Cox #include <linux/io.h>			/* For inb/outb/... */
64420b54deSMatt Fleming #include <linux/platform_data/itco_wdt.h>
6525f1ca31SMika Westerberg #include <linux/mfd/intel_pmc_bxt.h>
66b7e04f8cSWim Van Sebroeck 
670e6fa3fbSAlan Cox #include "iTCO_vendor.h"
68b7e04f8cSWim Van Sebroeck 
69b7e04f8cSWim Van Sebroeck /* Address definitions for the TCO */
700e6fa3fbSAlan Cox /* TCO base address */
71ce1b95caSGuenter Roeck #define TCOBASE(p)	((p)->tco_res->start)
720e6fa3fbSAlan Cox /* SMI Control and Enable Register */
73ce1b95caSGuenter Roeck #define SMI_EN(p)	((p)->smi_res->start)
74b7e04f8cSWim Van Sebroeck 
75ce1b95caSGuenter Roeck #define TCO_RLD(p)	(TCOBASE(p) + 0x00) /* TCO Timer Reload/Curr. Value */
76ce1b95caSGuenter Roeck #define TCOv1_TMR(p)	(TCOBASE(p) + 0x01) /* TCOv1 Timer Initial Value*/
77ce1b95caSGuenter Roeck #define TCO_DAT_IN(p)	(TCOBASE(p) + 0x02) /* TCO Data In Register	*/
78ce1b95caSGuenter Roeck #define TCO_DAT_OUT(p)	(TCOBASE(p) + 0x03) /* TCO Data Out Register	*/
79ce1b95caSGuenter Roeck #define TCO1_STS(p)	(TCOBASE(p) + 0x04) /* TCO1 Status Register	*/
80ce1b95caSGuenter Roeck #define TCO2_STS(p)	(TCOBASE(p) + 0x06) /* TCO2 Status Register	*/
81ce1b95caSGuenter Roeck #define TCO1_CNT(p)	(TCOBASE(p) + 0x08) /* TCO1 Control Register	*/
82ce1b95caSGuenter Roeck #define TCO2_CNT(p)	(TCOBASE(p) + 0x0a) /* TCO2 Control Register	*/
83ce1b95caSGuenter Roeck #define TCOv2_TMR(p)	(TCOBASE(p) + 0x12) /* TCOv2 Timer Initial Value*/
84b7e04f8cSWim Van Sebroeck 
85b7e04f8cSWim Van Sebroeck /* internal variables */
86ce1b95caSGuenter Roeck struct iTCO_wdt_private {
87ce1b95caSGuenter Roeck 	struct watchdog_device wddev;
88ce1b95caSGuenter Roeck 
890e6fa3fbSAlan Cox 	/* TCO version/generation */
900e6fa3fbSAlan Cox 	unsigned int iTCO_version;
91887c8ec7SAaron Sierra 	struct resource *tco_res;
92887c8ec7SAaron Sierra 	struct resource *smi_res;
9324b3a167SPeter Tyser 	/*
9424b3a167SPeter Tyser 	 * NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2),
9524b3a167SPeter Tyser 	 * or memory-mapped PMC register bit 4 (TCO version 3).
9624b3a167SPeter Tyser 	 */
9724b3a167SPeter Tyser 	unsigned long __iomem *gcs_pmc;
980e6fa3fbSAlan Cox 	/* the lock for io operations */
990e6fa3fbSAlan Cox 	spinlock_t io_lock;
1000e6fa3fbSAlan Cox 	/* the PCI-device */
10178e45696SGuenter Roeck 	struct pci_dev *pci_dev;
102f321c9cbSRafael J. Wysocki 	/* whether or not the watchdog has been suspended */
103f321c9cbSRafael J. Wysocki 	bool suspended;
104140c91b2SKuppuswamy Sathyanarayanan 	/* no reboot API private data */
105140c91b2SKuppuswamy Sathyanarayanan 	void *no_reboot_priv;
106f583a884SKuppuswamy Sathyanarayanan 	/* no reboot update function pointer */
107f583a884SKuppuswamy Sathyanarayanan 	int (*update_no_reboot_bit)(void *p, bool set);
108ce1b95caSGuenter Roeck };
109b7e04f8cSWim Van Sebroeck 
110b7e04f8cSWim Van Sebroeck /* module parameters */
111bff23431SWim Van Sebroeck #define WATCHDOG_TIMEOUT 30	/* 30 sec default heartbeat */
112bff23431SWim Van Sebroeck static int heartbeat = WATCHDOG_TIMEOUT;  /* in seconds */
113b7e04f8cSWim Van Sebroeck module_param(heartbeat, int, 0);
1147e6811daSPádraig Brady MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. "
1157e6811daSPádraig Brady 	"5..76 (TCO v1) or 3..614 (TCO v2), default="
116bff23431SWim Van Sebroeck 				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
117b7e04f8cSWim Van Sebroeck 
11886a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
11986a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
1200e6fa3fbSAlan Cox MODULE_PARM_DESC(nowayout,
1210e6fa3fbSAlan Cox 	"Watchdog cannot be stopped once started (default="
1220e6fa3fbSAlan Cox 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
123b7e04f8cSWim Van Sebroeck 
1240d098587SWim Van Sebroeck static int turn_SMI_watchdog_clear_off = 1;
125deb9197bSWim Van Sebroeck module_param(turn_SMI_watchdog_clear_off, int, 0);
126deb9197bSWim Van Sebroeck MODULE_PARM_DESC(turn_SMI_watchdog_clear_off,
1270d098587SWim Van Sebroeck 	"Turn off SMI clearing watchdog (depends on TCO-version)(default=1)");
128deb9197bSWim Van Sebroeck 
129b7e04f8cSWim Van Sebroeck /*
130b7e04f8cSWim Van Sebroeck  * Some TCO specific functions
131b7e04f8cSWim Van Sebroeck  */
132b7e04f8cSWim Van Sebroeck 
13324b3a167SPeter Tyser /*
13424b3a167SPeter Tyser  * The iTCO v1 and v2's internal timer is stored as ticks which decrement
13524b3a167SPeter Tyser  * every 0.6 seconds.  v3's internal timer is stored as seconds (some
13624b3a167SPeter Tyser  * datasheets incorrectly state 0.6 seconds).
13724b3a167SPeter Tyser  */
138ce1b95caSGuenter Roeck static inline unsigned int seconds_to_ticks(struct iTCO_wdt_private *p,
139ce1b95caSGuenter Roeck 					    int secs)
140b7e04f8cSWim Van Sebroeck {
141ce1b95caSGuenter Roeck 	return p->iTCO_version == 3 ? secs : (secs * 10) / 6;
14224b3a167SPeter Tyser }
14324b3a167SPeter Tyser 
144ce1b95caSGuenter Roeck static inline unsigned int ticks_to_seconds(struct iTCO_wdt_private *p,
145ce1b95caSGuenter Roeck 					    int ticks)
14624b3a167SPeter Tyser {
147ce1b95caSGuenter Roeck 	return p->iTCO_version == 3 ? ticks : (ticks * 6) / 10;
148b7e04f8cSWim Van Sebroeck }
149b7e04f8cSWim Van Sebroeck 
150ce1b95caSGuenter Roeck static inline u32 no_reboot_bit(struct iTCO_wdt_private *p)
1512a7a0e9bSMatt Fleming {
1522a7a0e9bSMatt Fleming 	u32 enable_bit;
1532a7a0e9bSMatt Fleming 
154ce1b95caSGuenter Roeck 	switch (p->iTCO_version) {
1553b3a1c8fSYong, Jonathan 	case 5:
1562a7a0e9bSMatt Fleming 	case 3:
1572a7a0e9bSMatt Fleming 		enable_bit = 0x00000010;
1582a7a0e9bSMatt Fleming 		break;
1592a7a0e9bSMatt Fleming 	case 2:
1602a7a0e9bSMatt Fleming 		enable_bit = 0x00000020;
1612a7a0e9bSMatt Fleming 		break;
1622a7a0e9bSMatt Fleming 	case 4:
1632a7a0e9bSMatt Fleming 	case 1:
1642a7a0e9bSMatt Fleming 	default:
1652a7a0e9bSMatt Fleming 		enable_bit = 0x00000002;
1662a7a0e9bSMatt Fleming 		break;
1672a7a0e9bSMatt Fleming 	}
1682a7a0e9bSMatt Fleming 
1692a7a0e9bSMatt Fleming 	return enable_bit;
1702a7a0e9bSMatt Fleming }
1712a7a0e9bSMatt Fleming 
172f583a884SKuppuswamy Sathyanarayanan static int update_no_reboot_bit_def(void *priv, bool set)
173b7e04f8cSWim Van Sebroeck {
174f583a884SKuppuswamy Sathyanarayanan 	return 0;
175b7e04f8cSWim Van Sebroeck }
176b7e04f8cSWim Van Sebroeck 
177f583a884SKuppuswamy Sathyanarayanan static int update_no_reboot_bit_pci(void *priv, bool set)
178b7e04f8cSWim Van Sebroeck {
179f583a884SKuppuswamy Sathyanarayanan 	struct iTCO_wdt_private *p = priv;
180f583a884SKuppuswamy Sathyanarayanan 	u32 val32 = 0, newval32 = 0;
181b7e04f8cSWim Van Sebroeck 
18278e45696SGuenter Roeck 	pci_read_config_dword(p->pci_dev, 0xd4, &val32);
183f583a884SKuppuswamy Sathyanarayanan 	if (set)
184f583a884SKuppuswamy Sathyanarayanan 		val32 |= no_reboot_bit(p);
185f583a884SKuppuswamy Sathyanarayanan 	else
186f583a884SKuppuswamy Sathyanarayanan 		val32 &= ~no_reboot_bit(p);
18778e45696SGuenter Roeck 	pci_write_config_dword(p->pci_dev, 0xd4, val32);
188f583a884SKuppuswamy Sathyanarayanan 	pci_read_config_dword(p->pci_dev, 0xd4, &newval32);
189b7e04f8cSWim Van Sebroeck 
190f583a884SKuppuswamy Sathyanarayanan 	/* make sure the update is successful */
191f583a884SKuppuswamy Sathyanarayanan 	if (val32 != newval32)
1922a7a0e9bSMatt Fleming 		return -EIO;
1932a7a0e9bSMatt Fleming 
1942a7a0e9bSMatt Fleming 	return 0;
195b7e04f8cSWim Van Sebroeck }
196b7e04f8cSWim Van Sebroeck 
197f583a884SKuppuswamy Sathyanarayanan static int update_no_reboot_bit_mem(void *priv, bool set)
198f583a884SKuppuswamy Sathyanarayanan {
199f583a884SKuppuswamy Sathyanarayanan 	struct iTCO_wdt_private *p = priv;
200f583a884SKuppuswamy Sathyanarayanan 	u32 val32 = 0, newval32 = 0;
201f583a884SKuppuswamy Sathyanarayanan 
202f583a884SKuppuswamy Sathyanarayanan 	val32 = readl(p->gcs_pmc);
203f583a884SKuppuswamy Sathyanarayanan 	if (set)
204f583a884SKuppuswamy Sathyanarayanan 		val32 |= no_reboot_bit(p);
205f583a884SKuppuswamy Sathyanarayanan 	else
206f583a884SKuppuswamy Sathyanarayanan 		val32 &= ~no_reboot_bit(p);
207f583a884SKuppuswamy Sathyanarayanan 	writel(val32, p->gcs_pmc);
208f583a884SKuppuswamy Sathyanarayanan 	newval32 = readl(p->gcs_pmc);
209f583a884SKuppuswamy Sathyanarayanan 
210f583a884SKuppuswamy Sathyanarayanan 	/* make sure the update is successful */
211f583a884SKuppuswamy Sathyanarayanan 	if (val32 != newval32)
212f583a884SKuppuswamy Sathyanarayanan 		return -EIO;
213f583a884SKuppuswamy Sathyanarayanan 
214f583a884SKuppuswamy Sathyanarayanan 	return 0;
215f583a884SKuppuswamy Sathyanarayanan }
216f583a884SKuppuswamy Sathyanarayanan 
217da23b6faSMika Westerberg static int update_no_reboot_bit_cnt(void *priv, bool set)
218da23b6faSMika Westerberg {
219da23b6faSMika Westerberg 	struct iTCO_wdt_private *p = priv;
220da23b6faSMika Westerberg 	u16 val, newval;
221da23b6faSMika Westerberg 
222da23b6faSMika Westerberg 	val = inw(TCO1_CNT(p));
223da23b6faSMika Westerberg 	if (set)
224da23b6faSMika Westerberg 		val |= BIT(0);
225da23b6faSMika Westerberg 	else
226da23b6faSMika Westerberg 		val &= ~BIT(0);
227da23b6faSMika Westerberg 	outw(val, TCO1_CNT(p));
228da23b6faSMika Westerberg 	newval = inw(TCO1_CNT(p));
229da23b6faSMika Westerberg 
230da23b6faSMika Westerberg 	/* make sure the update is successful */
231da23b6faSMika Westerberg 	return val != newval ? -EIO : 0;
232da23b6faSMika Westerberg }
233da23b6faSMika Westerberg 
23425f1ca31SMika Westerberg static int update_no_reboot_bit_pmc(void *priv, bool set)
23525f1ca31SMika Westerberg {
23625f1ca31SMika Westerberg 	struct intel_pmc_dev *pmc = priv;
23725f1ca31SMika Westerberg 	u32 bits = PMC_CFG_NO_REBOOT_EN;
23825f1ca31SMika Westerberg 	u32 value = set ? bits : 0;
23925f1ca31SMika Westerberg 
24025f1ca31SMika Westerberg 	return intel_pmc_gcr_update(pmc, PMC_GCR_PMC_CFG_REG, bits, value);
24125f1ca31SMika Westerberg }
24225f1ca31SMika Westerberg 
243140c91b2SKuppuswamy Sathyanarayanan static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p,
24425f1ca31SMika Westerberg 					 struct platform_device *pdev,
245140c91b2SKuppuswamy Sathyanarayanan 					 struct itco_wdt_platform_data *pdata)
246f583a884SKuppuswamy Sathyanarayanan {
24725f1ca31SMika Westerberg 	if (pdata->no_reboot_use_pmc) {
24825f1ca31SMika Westerberg 		struct intel_pmc_dev *pmc = dev_get_drvdata(pdev->dev.parent);
24925f1ca31SMika Westerberg 
25025f1ca31SMika Westerberg 		p->update_no_reboot_bit = update_no_reboot_bit_pmc;
25125f1ca31SMika Westerberg 		p->no_reboot_priv = pmc;
252140c91b2SKuppuswamy Sathyanarayanan 		return;
253140c91b2SKuppuswamy Sathyanarayanan 	}
254140c91b2SKuppuswamy Sathyanarayanan 
255da23b6faSMika Westerberg 	if (p->iTCO_version >= 6)
256da23b6faSMika Westerberg 		p->update_no_reboot_bit = update_no_reboot_bit_cnt;
257da23b6faSMika Westerberg 	else if (p->iTCO_version >= 2)
258f583a884SKuppuswamy Sathyanarayanan 		p->update_no_reboot_bit = update_no_reboot_bit_mem;
259f583a884SKuppuswamy Sathyanarayanan 	else if (p->iTCO_version == 1)
260f583a884SKuppuswamy Sathyanarayanan 		p->update_no_reboot_bit = update_no_reboot_bit_pci;
261f583a884SKuppuswamy Sathyanarayanan 	else
262f583a884SKuppuswamy Sathyanarayanan 		p->update_no_reboot_bit = update_no_reboot_bit_def;
263140c91b2SKuppuswamy Sathyanarayanan 
264140c91b2SKuppuswamy Sathyanarayanan 	p->no_reboot_priv = p;
265f583a884SKuppuswamy Sathyanarayanan }
266f583a884SKuppuswamy Sathyanarayanan 
267bff23431SWim Van Sebroeck static int iTCO_wdt_start(struct watchdog_device *wd_dev)
268b7e04f8cSWim Van Sebroeck {
269ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
270b7e04f8cSWim Van Sebroeck 	unsigned int val;
271b7e04f8cSWim Van Sebroeck 
272ce1b95caSGuenter Roeck 	spin_lock(&p->io_lock);
273b7e04f8cSWim Van Sebroeck 
274ce1b95caSGuenter Roeck 	iTCO_vendor_pre_start(p->smi_res, wd_dev->timeout);
275b7e04f8cSWim Van Sebroeck 
276b7e04f8cSWim Van Sebroeck 	/* disable chipset's NO_REBOOT bit */
277140c91b2SKuppuswamy Sathyanarayanan 	if (p->update_no_reboot_bit(p->no_reboot_priv, false)) {
278ce1b95caSGuenter Roeck 		spin_unlock(&p->io_lock);
279c21172b3SEnrico Weigelt, metux IT consult 		dev_err(wd_dev->parent, "failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n");
280b7e04f8cSWim Van Sebroeck 		return -EIO;
281b7e04f8cSWim Van Sebroeck 	}
282b7e04f8cSWim Van Sebroeck 
2837cd5b08bSWim Van Sebroeck 	/* Force the timer to its reload value by writing to the TCO_RLD
2847cd5b08bSWim Van Sebroeck 	   register */
285ce1b95caSGuenter Roeck 	if (p->iTCO_version >= 2)
286ce1b95caSGuenter Roeck 		outw(0x01, TCO_RLD(p));
287ce1b95caSGuenter Roeck 	else if (p->iTCO_version == 1)
288ce1b95caSGuenter Roeck 		outb(0x01, TCO_RLD(p));
2897cd5b08bSWim Van Sebroeck 
290b7e04f8cSWim Van Sebroeck 	/* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */
291ce1b95caSGuenter Roeck 	val = inw(TCO1_CNT(p));
292b7e04f8cSWim Van Sebroeck 	val &= 0xf7ff;
293ce1b95caSGuenter Roeck 	outw(val, TCO1_CNT(p));
294ce1b95caSGuenter Roeck 	val = inw(TCO1_CNT(p));
295ce1b95caSGuenter Roeck 	spin_unlock(&p->io_lock);
296b7e04f8cSWim Van Sebroeck 
297b7e04f8cSWim Van Sebroeck 	if (val & 0x0800)
298b7e04f8cSWim Van Sebroeck 		return -1;
299b7e04f8cSWim Van Sebroeck 	return 0;
300b7e04f8cSWim Van Sebroeck }
301b7e04f8cSWim Van Sebroeck 
302bff23431SWim Van Sebroeck static int iTCO_wdt_stop(struct watchdog_device *wd_dev)
303b7e04f8cSWim Van Sebroeck {
304ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
305b7e04f8cSWim Van Sebroeck 	unsigned int val;
306b7e04f8cSWim Van Sebroeck 
307ce1b95caSGuenter Roeck 	spin_lock(&p->io_lock);
308b7e04f8cSWim Van Sebroeck 
309ce1b95caSGuenter Roeck 	iTCO_vendor_pre_stop(p->smi_res);
310b7e04f8cSWim Van Sebroeck 
311b7e04f8cSWim Van Sebroeck 	/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
312ce1b95caSGuenter Roeck 	val = inw(TCO1_CNT(p));
313b7e04f8cSWim Van Sebroeck 	val |= 0x0800;
314ce1b95caSGuenter Roeck 	outw(val, TCO1_CNT(p));
315ce1b95caSGuenter Roeck 	val = inw(TCO1_CNT(p));
316b7e04f8cSWim Van Sebroeck 
317b7e04f8cSWim Van Sebroeck 	/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
318140c91b2SKuppuswamy Sathyanarayanan 	p->update_no_reboot_bit(p->no_reboot_priv, true);
319b7e04f8cSWim Van Sebroeck 
320ce1b95caSGuenter Roeck 	spin_unlock(&p->io_lock);
321b7e04f8cSWim Van Sebroeck 
322b7e04f8cSWim Van Sebroeck 	if ((val & 0x0800) == 0)
323b7e04f8cSWim Van Sebroeck 		return -1;
324b7e04f8cSWim Van Sebroeck 	return 0;
325b7e04f8cSWim Van Sebroeck }
326b7e04f8cSWim Van Sebroeck 
327bff23431SWim Van Sebroeck static int iTCO_wdt_ping(struct watchdog_device *wd_dev)
328b7e04f8cSWim Van Sebroeck {
329ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
330b7e04f8cSWim Van Sebroeck 
331ce1b95caSGuenter Roeck 	spin_lock(&p->io_lock);
332ce1b95caSGuenter Roeck 
333fc61e83aSWim Van Sebroeck 	/* Reload the timer by writing to the TCO Timer Counter register */
334fc61e83aSWim Van Sebroeck 	if (p->iTCO_version >= 2) {
335fc61e83aSWim Van Sebroeck 		outw(0x01, TCO_RLD(p));
336fc61e83aSWim Van Sebroeck 	} else if (p->iTCO_version == 1) {
3377e6811daSPádraig Brady 		/* Reset the timeout status bit so that the timer
3387e6811daSPádraig Brady 		 * needs to count down twice again before rebooting */
339ce1b95caSGuenter Roeck 		outw(0x0008, TCO1_STS(p));	/* write 1 to clear bit */
3407e6811daSPádraig Brady 
341ce1b95caSGuenter Roeck 		outb(0x01, TCO_RLD(p));
342fc61e83aSWim Van Sebroeck 	}
343b7e04f8cSWim Van Sebroeck 
344ce1b95caSGuenter Roeck 	spin_unlock(&p->io_lock);
345b7e04f8cSWim Van Sebroeck 	return 0;
346b7e04f8cSWim Van Sebroeck }
347b7e04f8cSWim Van Sebroeck 
348bff23431SWim Van Sebroeck static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t)
349b7e04f8cSWim Van Sebroeck {
350ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
351b7e04f8cSWim Van Sebroeck 	unsigned int val16;
352b7e04f8cSWim Van Sebroeck 	unsigned char val8;
353b7e04f8cSWim Van Sebroeck 	unsigned int tmrval;
354b7e04f8cSWim Van Sebroeck 
355fc61e83aSWim Van Sebroeck 	tmrval = seconds_to_ticks(p, t);
356fc61e83aSWim Van Sebroeck 
3576e7733efSGuenter Roeck 	/* For TCO v1 the timer counts down twice before rebooting */
3586e7733efSGuenter Roeck 	if (p->iTCO_version == 1)
359fc61e83aSWim Van Sebroeck 		tmrval /= 2;
3607e6811daSPádraig Brady 
361b7e04f8cSWim Van Sebroeck 	/* from the specs: */
362b7e04f8cSWim Van Sebroeck 	/* "Values of 0h-3h are ignored and should not be attempted" */
363b7e04f8cSWim Van Sebroeck 	if (tmrval < 0x04)
364b7e04f8cSWim Van Sebroeck 		return -EINVAL;
365ce1b95caSGuenter Roeck 	if ((p->iTCO_version >= 2 && tmrval > 0x3ff) ||
366ce1b95caSGuenter Roeck 	    (p->iTCO_version == 1 && tmrval > 0x03f))
367b7e04f8cSWim Van Sebroeck 		return -EINVAL;
368b7e04f8cSWim Van Sebroeck 
369b7e04f8cSWim Van Sebroeck 	/* Write new heartbeat to watchdog */
370ce1b95caSGuenter Roeck 	if (p->iTCO_version >= 2) {
371ce1b95caSGuenter Roeck 		spin_lock(&p->io_lock);
372ce1b95caSGuenter Roeck 		val16 = inw(TCOv2_TMR(p));
373b7e04f8cSWim Van Sebroeck 		val16 &= 0xfc00;
374b7e04f8cSWim Van Sebroeck 		val16 |= tmrval;
375ce1b95caSGuenter Roeck 		outw(val16, TCOv2_TMR(p));
376ce1b95caSGuenter Roeck 		val16 = inw(TCOv2_TMR(p));
377ce1b95caSGuenter Roeck 		spin_unlock(&p->io_lock);
378b7e04f8cSWim Van Sebroeck 
379b7e04f8cSWim Van Sebroeck 		if ((val16 & 0x3ff) != tmrval)
380b7e04f8cSWim Van Sebroeck 			return -EINVAL;
381ce1b95caSGuenter Roeck 	} else if (p->iTCO_version == 1) {
382ce1b95caSGuenter Roeck 		spin_lock(&p->io_lock);
383ce1b95caSGuenter Roeck 		val8 = inb(TCOv1_TMR(p));
384b7e04f8cSWim Van Sebroeck 		val8 &= 0xc0;
385b7e04f8cSWim Van Sebroeck 		val8 |= (tmrval & 0xff);
386ce1b95caSGuenter Roeck 		outb(val8, TCOv1_TMR(p));
387ce1b95caSGuenter Roeck 		val8 = inb(TCOv1_TMR(p));
388ce1b95caSGuenter Roeck 		spin_unlock(&p->io_lock);
389b7e04f8cSWim Van Sebroeck 
390b7e04f8cSWim Van Sebroeck 		if ((val8 & 0x3f) != tmrval)
391b7e04f8cSWim Van Sebroeck 			return -EINVAL;
392b7e04f8cSWim Van Sebroeck 	}
393b7e04f8cSWim Van Sebroeck 
394bff23431SWim Van Sebroeck 	wd_dev->timeout = t;
395b7e04f8cSWim Van Sebroeck 	return 0;
396b7e04f8cSWim Van Sebroeck }
397b7e04f8cSWim Van Sebroeck 
398bff23431SWim Van Sebroeck static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev)
399b7e04f8cSWim Van Sebroeck {
400ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
401b7e04f8cSWim Van Sebroeck 	unsigned int val16;
402b7e04f8cSWim Van Sebroeck 	unsigned char val8;
403bff23431SWim Van Sebroeck 	unsigned int time_left = 0;
404b7e04f8cSWim Van Sebroeck 
405b7e04f8cSWim Van Sebroeck 	/* read the TCO Timer */
406ce1b95caSGuenter Roeck 	if (p->iTCO_version >= 2) {
407ce1b95caSGuenter Roeck 		spin_lock(&p->io_lock);
408ce1b95caSGuenter Roeck 		val16 = inw(TCO_RLD(p));
409b7e04f8cSWim Van Sebroeck 		val16 &= 0x3ff;
410ce1b95caSGuenter Roeck 		spin_unlock(&p->io_lock);
411b7e04f8cSWim Van Sebroeck 
412ce1b95caSGuenter Roeck 		time_left = ticks_to_seconds(p, val16);
413ce1b95caSGuenter Roeck 	} else if (p->iTCO_version == 1) {
414ce1b95caSGuenter Roeck 		spin_lock(&p->io_lock);
415ce1b95caSGuenter Roeck 		val8 = inb(TCO_RLD(p));
416b7e04f8cSWim Van Sebroeck 		val8 &= 0x3f;
417ce1b95caSGuenter Roeck 		if (!(inw(TCO1_STS(p)) & 0x0008))
418ce1b95caSGuenter Roeck 			val8 += (inb(TCOv1_TMR(p)) & 0x3f);
419ce1b95caSGuenter Roeck 		spin_unlock(&p->io_lock);
420b7e04f8cSWim Van Sebroeck 
421ce1b95caSGuenter Roeck 		time_left = ticks_to_seconds(p, val8);
422bff23431SWim Van Sebroeck 	}
423bff23431SWim Van Sebroeck 	return time_left;
424b7e04f8cSWim Van Sebroeck }
425b7e04f8cSWim Van Sebroeck 
426ef9b7bf5SMika Westerberg /* Returns true if the watchdog was running */
427ef9b7bf5SMika Westerberg static bool iTCO_wdt_set_running(struct iTCO_wdt_private *p)
4281ae3e78cSMika Westerberg {
4291ae3e78cSMika Westerberg 	u16 val;
4301ae3e78cSMika Westerberg 
431ef9b7bf5SMika Westerberg 	/* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled */
4321ae3e78cSMika Westerberg 	val = inw(TCO1_CNT(p));
433ef9b7bf5SMika Westerberg 	if (!(val & BIT(11))) {
4341ae3e78cSMika Westerberg 		set_bit(WDOG_HW_RUNNING, &p->wddev.status);
435ef9b7bf5SMika Westerberg 		return true;
436ef9b7bf5SMika Westerberg 	}
437ef9b7bf5SMika Westerberg 	return false;
4381ae3e78cSMika Westerberg }
4391ae3e78cSMika Westerberg 
440b7e04f8cSWim Van Sebroeck /*
441bff23431SWim Van Sebroeck  *	Kernel Interfaces
442b7e04f8cSWim Van Sebroeck  */
443b7e04f8cSWim Van Sebroeck 
4444ea6b986SThomas Weißschuh static struct watchdog_info ident = {
445b7e04f8cSWim Van Sebroeck 	.options =		WDIOF_SETTIMEOUT |
446b7e04f8cSWim Van Sebroeck 				WDIOF_KEEPALIVEPING |
447b7e04f8cSWim Van Sebroeck 				WDIOF_MAGICCLOSE,
448b7e04f8cSWim Van Sebroeck 	.identity =		DRV_NAME,
449b7e04f8cSWim Van Sebroeck };
450b7e04f8cSWim Van Sebroeck 
451bff23431SWim Van Sebroeck static const struct watchdog_ops iTCO_wdt_ops = {
452b7e04f8cSWim Van Sebroeck 	.owner =		THIS_MODULE,
453bff23431SWim Van Sebroeck 	.start =		iTCO_wdt_start,
454bff23431SWim Van Sebroeck 	.stop =			iTCO_wdt_stop,
455bff23431SWim Van Sebroeck 	.ping =			iTCO_wdt_ping,
456bff23431SWim Van Sebroeck 	.set_timeout =		iTCO_wdt_set_timeout,
457bff23431SWim Van Sebroeck 	.get_timeleft =		iTCO_wdt_get_timeleft,
458b7e04f8cSWim Van Sebroeck };
459b7e04f8cSWim Van Sebroeck 
460b7e04f8cSWim Van Sebroeck /*
461b7e04f8cSWim Van Sebroeck  *	Init & exit routines
462b7e04f8cSWim Van Sebroeck  */
463b7e04f8cSWim Van Sebroeck 
46478e45696SGuenter Roeck static int iTCO_wdt_probe(struct platform_device *pdev)
465887c8ec7SAaron Sierra {
46678e45696SGuenter Roeck 	struct device *dev = &pdev->dev;
46778e45696SGuenter Roeck 	struct itco_wdt_platform_data *pdata = dev_get_platdata(dev);
468ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p;
469ce1b95caSGuenter Roeck 	unsigned long val32;
470ce1b95caSGuenter Roeck 	int ret;
471887c8ec7SAaron Sierra 
472420b54deSMatt Fleming 	if (!pdata)
473ce1b95caSGuenter Roeck 		return -ENODEV;
474887c8ec7SAaron Sierra 
47578e45696SGuenter Roeck 	p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
476ce1b95caSGuenter Roeck 	if (!p)
477ce1b95caSGuenter Roeck 		return -ENOMEM;
478887c8ec7SAaron Sierra 
479ce1b95caSGuenter Roeck 	spin_lock_init(&p->io_lock);
480887c8ec7SAaron Sierra 
48178e45696SGuenter Roeck 	p->tco_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_TCO);
482ce1b95caSGuenter Roeck 	if (!p->tco_res)
483ce1b95caSGuenter Roeck 		return -ENODEV;
484887c8ec7SAaron Sierra 
485ce1b95caSGuenter Roeck 	p->iTCO_version = pdata->version;
48678e45696SGuenter Roeck 	p->pci_dev = to_pci_dev(dev->parent);
487b7e04f8cSWim Van Sebroeck 
488e42b0c24SMika Westerberg 	p->smi_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_SMI);
489e42b0c24SMika Westerberg 	if (p->smi_res) {
490e42b0c24SMika Westerberg 		/* The TCO logic uses the TCO_EN bit in the SMI_EN register */
491e42b0c24SMika Westerberg 		if (!devm_request_region(dev, p->smi_res->start,
492e42b0c24SMika Westerberg 					 resource_size(p->smi_res),
493e42b0c24SMika Westerberg 					 pdev->name)) {
494cf813c67SEnrico Weigelt, metux IT consult 			dev_err(dev, "I/O address 0x%04llx already in use, device disabled\n",
495e42b0c24SMika Westerberg 			       (u64)SMI_EN(p));
496e42b0c24SMika Westerberg 			return -EBUSY;
497e42b0c24SMika Westerberg 		}
498e42b0c24SMika Westerberg 	} else if (iTCO_vendorsupport ||
499e42b0c24SMika Westerberg 		   turn_SMI_watchdog_clear_off >= p->iTCO_version) {
500cf813c67SEnrico Weigelt, metux IT consult 		dev_err(dev, "SMI I/O resource is missing\n");
501e42b0c24SMika Westerberg 		return -ENODEV;
502e42b0c24SMika Westerberg 	}
503e42b0c24SMika Westerberg 
50425f1ca31SMika Westerberg 	iTCO_wdt_no_reboot_bit_setup(p, pdev, pdata);
505f583a884SKuppuswamy Sathyanarayanan 
506b7e04f8cSWim Van Sebroeck 	/*
50724b3a167SPeter Tyser 	 * Get the Memory-Mapped GCS or PMC register, we need it for the
50824b3a167SPeter Tyser 	 * NO_REBOOT flag (TCO v2 and v3).
509b7e04f8cSWim Van Sebroeck 	 */
510da23b6faSMika Westerberg 	if (p->iTCO_version >= 2 && p->iTCO_version < 6 &&
51125f1ca31SMika Westerberg 	    !pdata->no_reboot_use_pmc) {
51279cc4d22SCai Huoqing 		p->gcs_pmc = devm_platform_ioremap_resource(pdev, ICH_RES_MEM_GCS_PMC);
513c7bbcc87SGuenter Roeck 		if (IS_ERR(p->gcs_pmc))
514c7bbcc87SGuenter Roeck 			return PTR_ERR(p->gcs_pmc);
515b7e04f8cSWim Van Sebroeck 	}
516b7e04f8cSWim Van Sebroeck 
517b7e04f8cSWim Van Sebroeck 	/* Check chipset's NO_REBOOT bit */
518140c91b2SKuppuswamy Sathyanarayanan 	if (p->update_no_reboot_bit(p->no_reboot_priv, false) &&
519ce1b95caSGuenter Roeck 	    iTCO_vendor_check_noreboot_on()) {
520c21172b3SEnrico Weigelt, metux IT consult 		dev_info(dev, "unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n");
521c7bbcc87SGuenter Roeck 		return -ENODEV;	/* Cannot reset NO_REBOOT bit */
522b7e04f8cSWim Van Sebroeck 	}
523b7e04f8cSWim Van Sebroeck 
524ce1b95caSGuenter Roeck 	if (turn_SMI_watchdog_clear_off >= p->iTCO_version) {
525887c8ec7SAaron Sierra 		/*
526887c8ec7SAaron Sierra 		 * Bit 13: TCO_EN -> 0
527887c8ec7SAaron Sierra 		 * Disables TCO logic generating an SMI#
528887c8ec7SAaron Sierra 		 */
529ce1b95caSGuenter Roeck 		val32 = inl(SMI_EN(p));
5306e7733efSGuenter Roeck 		val32 &= 0xffffdfff;	/* Turn off SMI clearing watchdog */
531ce1b95caSGuenter Roeck 		outl(val32, SMI_EN(p));
532deb9197bSWim Van Sebroeck 	}
533b7e04f8cSWim Van Sebroeck 
53478e45696SGuenter Roeck 	if (!devm_request_region(dev, p->tco_res->start,
535c7bbcc87SGuenter Roeck 				 resource_size(p->tco_res),
53678e45696SGuenter Roeck 				 pdev->name)) {
537c21172b3SEnrico Weigelt, metux IT consult 		dev_err(dev, "I/O address 0x%04llx already in use, device disabled\n",
538ce1b95caSGuenter Roeck 		       (u64)TCOBASE(p));
539c7bbcc87SGuenter Roeck 		return -EBUSY;
540b7e04f8cSWim Van Sebroeck 	}
541b7e04f8cSWim Van Sebroeck 
542c21172b3SEnrico Weigelt, metux IT consult 	dev_info(dev, "Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n",
543ce1b95caSGuenter Roeck 		pdata->name, pdata->version, (u64)TCOBASE(p));
544b7e04f8cSWim Van Sebroeck 
545b7e04f8cSWim Van Sebroeck 	/* Clear out the (probably old) status */
546ce1b95caSGuenter Roeck 	switch (p->iTCO_version) {
547da23b6faSMika Westerberg 	case 6:
5483b3a1c8fSYong, Jonathan 	case 5:
5492a7a0e9bSMatt Fleming 	case 4:
550ce1b95caSGuenter Roeck 		outw(0x0008, TCO1_STS(p)); /* Clear the Time Out Status bit */
551ce1b95caSGuenter Roeck 		outw(0x0002, TCO2_STS(p)); /* Clear SECOND_TO_STS bit */
5522a7a0e9bSMatt Fleming 		break;
5532a7a0e9bSMatt Fleming 	case 3:
554ce1b95caSGuenter Roeck 		outl(0x20008, TCO1_STS(p));
5552a7a0e9bSMatt Fleming 		break;
5562a7a0e9bSMatt Fleming 	case 2:
5572a7a0e9bSMatt Fleming 	case 1:
5582a7a0e9bSMatt Fleming 	default:
559ce1b95caSGuenter Roeck 		outw(0x0008, TCO1_STS(p)); /* Clear the Time Out Status bit */
560ce1b95caSGuenter Roeck 		outw(0x0002, TCO2_STS(p)); /* Clear SECOND_TO_STS bit */
561ce1b95caSGuenter Roeck 		outw(0x0004, TCO2_STS(p)); /* Clear BOOT_STS bit */
5622a7a0e9bSMatt Fleming 		break;
56324b3a167SPeter Tyser 	}
564b7e04f8cSWim Van Sebroeck 
5654ea6b986SThomas Weißschuh 	ident.firmware_version = p->iTCO_version;
566*35ff0ebfSChen Ni 	p->wddev.info = &ident;
567*35ff0ebfSChen Ni 	p->wddev.ops = &iTCO_wdt_ops;
568ce1b95caSGuenter Roeck 	p->wddev.bootstatus = 0;
569ce1b95caSGuenter Roeck 	p->wddev.timeout = WATCHDOG_TIMEOUT;
570ce1b95caSGuenter Roeck 	watchdog_set_nowayout(&p->wddev, nowayout);
57178e45696SGuenter Roeck 	p->wddev.parent = dev;
572ce1b95caSGuenter Roeck 
573ce1b95caSGuenter Roeck 	watchdog_set_drvdata(&p->wddev, p);
57478e45696SGuenter Roeck 	platform_set_drvdata(pdev, p);
575bff23431SWim Van Sebroeck 
576ef9b7bf5SMika Westerberg 	if (!iTCO_wdt_set_running(p)) {
577ef9b7bf5SMika Westerberg 		/*
578ef9b7bf5SMika Westerberg 		 * If the watchdog was not running set NO_REBOOT now to
579ef9b7bf5SMika Westerberg 		 * prevent later reboots.
580ef9b7bf5SMika Westerberg 		 */
581ef9b7bf5SMika Westerberg 		p->update_no_reboot_bit(p->no_reboot_priv, true);
582ef9b7bf5SMika Westerberg 	}
583b7e04f8cSWim Van Sebroeck 
5840e6fa3fbSAlan Cox 	/* Check that the heartbeat value is within it's range;
5850e6fa3fbSAlan Cox 	   if not reset to the default */
586ce1b95caSGuenter Roeck 	if (iTCO_wdt_set_timeout(&p->wddev, heartbeat)) {
587ce1b95caSGuenter Roeck 		iTCO_wdt_set_timeout(&p->wddev, WATCHDOG_TIMEOUT);
588c21172b3SEnrico Weigelt, metux IT consult 		dev_info(dev, "timeout value out of range, using %d\n",
589bff23431SWim Van Sebroeck 			WATCHDOG_TIMEOUT);
590b7e04f8cSWim Van Sebroeck 	}
591b7e04f8cSWim Van Sebroeck 
592d3d77b5aSGuenter Roeck 	watchdog_stop_on_reboot(&p->wddev);
59377d9f766SGuenter Roeck 	watchdog_stop_on_unregister(&p->wddev);
59478e45696SGuenter Roeck 	ret = devm_watchdog_register_device(dev, &p->wddev);
595b7e04f8cSWim Van Sebroeck 	if (ret != 0) {
596c21172b3SEnrico Weigelt, metux IT consult 		dev_err(dev, "cannot register watchdog device (err=%d)\n", ret);
597c7bbcc87SGuenter Roeck 		return ret;
598b7e04f8cSWim Van Sebroeck 	}
599b7e04f8cSWim Van Sebroeck 
600c21172b3SEnrico Weigelt, metux IT consult 	dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
601b7e04f8cSWim Van Sebroeck 		heartbeat, nowayout);
602b7e04f8cSWim Van Sebroeck 
603b7e04f8cSWim Van Sebroeck 	return 0;
604b7e04f8cSWim Van Sebroeck }
605b7e04f8cSWim Van Sebroeck 
606f321c9cbSRafael J. Wysocki /*
607f321c9cbSRafael J. Wysocki  * Suspend-to-idle requires this, because it stops the ticks and timekeeping, so
608f321c9cbSRafael J. Wysocki  * the watchdog cannot be pinged while in that state.  In ACPI sleep states the
609f321c9cbSRafael J. Wysocki  * watchdog is stopped by the platform firmware.
610f321c9cbSRafael J. Wysocki  */
611f321c9cbSRafael J. Wysocki 
612f321c9cbSRafael J. Wysocki #ifdef CONFIG_ACPI
6139ef95892SLiu Xinpeng static inline bool __maybe_unused need_suspend(void)
614f321c9cbSRafael J. Wysocki {
615f321c9cbSRafael J. Wysocki 	return acpi_target_system_state() == ACPI_STATE_S0;
616f321c9cbSRafael J. Wysocki }
617f321c9cbSRafael J. Wysocki #else
6189ef95892SLiu Xinpeng static inline bool __maybe_unused need_suspend(void) { return true; }
619f321c9cbSRafael J. Wysocki #endif
620f321c9cbSRafael J. Wysocki 
6219ef95892SLiu Xinpeng static int __maybe_unused iTCO_wdt_suspend_noirq(struct device *dev)
622f321c9cbSRafael J. Wysocki {
623ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = dev_get_drvdata(dev);
624f321c9cbSRafael J. Wysocki 	int ret = 0;
625f321c9cbSRafael J. Wysocki 
626ce1b95caSGuenter Roeck 	p->suspended = false;
627ce1b95caSGuenter Roeck 	if (watchdog_active(&p->wddev) && need_suspend()) {
628ce1b95caSGuenter Roeck 		ret = iTCO_wdt_stop(&p->wddev);
629f321c9cbSRafael J. Wysocki 		if (!ret)
630ce1b95caSGuenter Roeck 			p->suspended = true;
631f321c9cbSRafael J. Wysocki 	}
632f321c9cbSRafael J. Wysocki 	return ret;
633f321c9cbSRafael J. Wysocki }
634f321c9cbSRafael J. Wysocki 
6359ef95892SLiu Xinpeng static int __maybe_unused iTCO_wdt_resume_noirq(struct device *dev)
636f321c9cbSRafael J. Wysocki {
637ce1b95caSGuenter Roeck 	struct iTCO_wdt_private *p = dev_get_drvdata(dev);
638ce1b95caSGuenter Roeck 
639ce1b95caSGuenter Roeck 	if (p->suspended)
640ce1b95caSGuenter Roeck 		iTCO_wdt_start(&p->wddev);
641f321c9cbSRafael J. Wysocki 
642f321c9cbSRafael J. Wysocki 	return 0;
643f321c9cbSRafael J. Wysocki }
644f321c9cbSRafael J. Wysocki 
6456e938f6eSJulia Lawall static const struct dev_pm_ops iTCO_wdt_pm = {
6469ef95892SLiu Xinpeng 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(iTCO_wdt_suspend_noirq,
6479ef95892SLiu Xinpeng 				      iTCO_wdt_resume_noirq)
648f321c9cbSRafael J. Wysocki };
649f321c9cbSRafael J. Wysocki 
650b7e04f8cSWim Van Sebroeck static struct platform_driver iTCO_wdt_driver = {
651b7e04f8cSWim Van Sebroeck 	.probe          = iTCO_wdt_probe,
652b7e04f8cSWim Van Sebroeck 	.driver         = {
653b7e04f8cSWim Van Sebroeck 		.name   = DRV_NAME,
6549ef95892SLiu Xinpeng 		.pm     = &iTCO_wdt_pm,
655b7e04f8cSWim Van Sebroeck 	},
656b7e04f8cSWim Van Sebroeck };
657b7e04f8cSWim Van Sebroeck 
65889c866f5SEnrico Weigelt, metux IT consult module_platform_driver(iTCO_wdt_driver);
659b7e04f8cSWim Van Sebroeck 
660b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
661b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver");
662b7e04f8cSWim Van Sebroeck MODULE_VERSION(DRV_VERSION);
663b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
664e5de32e3SJan Beulich MODULE_ALIAS("platform:" DRV_NAME);
665