xref: /linux/drivers/watchdog/sp5100_tco.c (revision cbecf716ca618fd44feda6bd9a64a8179d031fc5)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
215e28bf1SPriyanka Gupta /*
315e28bf1SPriyanka Gupta  *	sp5100_tco :	TCO timer driver for sp5100 chipsets
415e28bf1SPriyanka Gupta  *
515e28bf1SPriyanka Gupta  *	(c) Copyright 2009 Google Inc., All Rights Reserved.
615e28bf1SPriyanka Gupta  *
715e28bf1SPriyanka Gupta  *	Based on i8xx_tco.c:
815e28bf1SPriyanka Gupta  *	(c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
915e28bf1SPriyanka Gupta  *	Reserved.
102ab77a34SAlexander A. Klimov  *				https://www.kernelconcepts.de
1115e28bf1SPriyanka Gupta  *
12740fbddfSTakahisa Tanaka  *	See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
13740fbddfSTakahisa Tanaka  *	    AMD Publication 45482 "AMD SB800-Series Southbridges Register
14740fbddfSTakahisa Tanaka  *	                                                      Reference Guide"
15887d2ec5SGuenter Roeck  *	    AMD Publication 48751 "BIOS and Kernel Developer’s Guide (BKDG)
16887d2ec5SGuenter Roeck  *				for AMD Family 16h Models 00h-0Fh Processors"
17887d2ec5SGuenter Roeck  *	    AMD Publication 51192 "AMD Bolton FCH Register Reference Guide"
18887d2ec5SGuenter Roeck  *	    AMD Publication 52740 "BIOS and Kernel Developer’s Guide (BKDG)
19887d2ec5SGuenter Roeck  *				for AMD Family 16h Models 30h-3Fh Processors"
20*09da89abSGuenter Roeck  *	    AMD Publication 55570-B1-PUB "Processor Programming Reference (PPR)
21*09da89abSGuenter Roeck  *				for AMD Family 17h Model 18h, Revision B1
22*09da89abSGuenter Roeck  *				Processors (PUB)
23*09da89abSGuenter Roeck  *	    AMD Publication 55772-A1-PUB "Processor Programming Reference (PPR)
24*09da89abSGuenter Roeck  *				for AMD Family 17h Model 20h, Revision A1
25*09da89abSGuenter Roeck  *				Processors (PUB)
2615e28bf1SPriyanka Gupta  */
2715e28bf1SPriyanka Gupta 
2815e28bf1SPriyanka Gupta /*
2915e28bf1SPriyanka Gupta  *	Includes, defines, variables, module parameters, ...
3015e28bf1SPriyanka Gupta  */
3115e28bf1SPriyanka Gupta 
3227c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
3327c766aaSJoe Perches 
347cd9d5ffSGuenter Roeck #include <linux/init.h>
357cd9d5ffSGuenter Roeck #include <linux/io.h>
367cd9d5ffSGuenter Roeck #include <linux/ioport.h>
3715e28bf1SPriyanka Gupta #include <linux/module.h>
3815e28bf1SPriyanka Gupta #include <linux/moduleparam.h>
3915e28bf1SPriyanka Gupta #include <linux/pci.h>
4015e28bf1SPriyanka Gupta #include <linux/platform_device.h>
417cd9d5ffSGuenter Roeck #include <linux/types.h>
427cd9d5ffSGuenter Roeck #include <linux/watchdog.h>
4315e28bf1SPriyanka Gupta 
4415e28bf1SPriyanka Gupta #include "sp5100_tco.h"
4515e28bf1SPriyanka Gupta 
467cd9d5ffSGuenter Roeck #define TCO_DRIVER_NAME	"sp5100-tco"
4715e28bf1SPriyanka Gupta 
4815e28bf1SPriyanka Gupta /* internal variables */
497cd9d5ffSGuenter Roeck 
50887d2ec5SGuenter Roeck enum tco_reg_layout {
51887d2ec5SGuenter Roeck 	sp5100, sb800, efch
52887d2ec5SGuenter Roeck };
53887d2ec5SGuenter Roeck 
547cd9d5ffSGuenter Roeck struct sp5100_tco {
557cd9d5ffSGuenter Roeck 	struct watchdog_device wdd;
567cd9d5ffSGuenter Roeck 	void __iomem *tcobase;
57887d2ec5SGuenter Roeck 	enum tco_reg_layout tco_reg_layout;
587cd9d5ffSGuenter Roeck };
5915e28bf1SPriyanka Gupta 
6015e28bf1SPriyanka Gupta /* the watchdog platform device */
6115e28bf1SPriyanka Gupta static struct platform_device *sp5100_tco_platform_device;
627cd9d5ffSGuenter Roeck /* the associated PCI device */
637cd9d5ffSGuenter Roeck static struct pci_dev *sp5100_tco_pci;
6415e28bf1SPriyanka Gupta 
6515e28bf1SPriyanka Gupta /* module parameters */
6615e28bf1SPriyanka Gupta 
6715e28bf1SPriyanka Gupta #define WATCHDOG_HEARTBEAT 60	/* 60 sec default heartbeat. */
6815e28bf1SPriyanka Gupta static int heartbeat = WATCHDOG_HEARTBEAT;  /* in seconds */
6915e28bf1SPriyanka Gupta module_param(heartbeat, int, 0);
7015e28bf1SPriyanka Gupta MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
7115e28bf1SPriyanka Gupta 		 __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
7215e28bf1SPriyanka Gupta 
7386a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
7486a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
75740fbddfSTakahisa Tanaka MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."
7615e28bf1SPriyanka Gupta 		" (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
7715e28bf1SPriyanka Gupta 
7815e28bf1SPriyanka Gupta /*
7915e28bf1SPriyanka Gupta  * Some TCO specific functions
8015e28bf1SPriyanka Gupta  */
8146856fabSLucas Stach 
82887d2ec5SGuenter Roeck static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev)
8346856fabSLucas Stach {
84887d2ec5SGuenter Roeck 	if (dev->vendor == PCI_VENDOR_ID_ATI &&
85887d2ec5SGuenter Roeck 	    dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
86887d2ec5SGuenter Roeck 	    dev->revision < 0x40) {
87887d2ec5SGuenter Roeck 		return sp5100;
88887d2ec5SGuenter Roeck 	} else if (dev->vendor == PCI_VENDOR_ID_AMD &&
89887d2ec5SGuenter Roeck 	    ((dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS &&
90887d2ec5SGuenter Roeck 	     dev->revision >= 0x41) ||
91887d2ec5SGuenter Roeck 	    (dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
92887d2ec5SGuenter Roeck 	     dev->revision >= 0x49))) {
93887d2ec5SGuenter Roeck 		return efch;
94887d2ec5SGuenter Roeck 	}
95887d2ec5SGuenter Roeck 	return sb800;
9646856fabSLucas Stach }
9746856fabSLucas Stach 
987cd9d5ffSGuenter Roeck static int tco_timer_start(struct watchdog_device *wdd)
9915e28bf1SPriyanka Gupta {
1007cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
10115e28bf1SPriyanka Gupta 	u32 val;
10215e28bf1SPriyanka Gupta 
1037cd9d5ffSGuenter Roeck 	val = readl(SP5100_WDT_CONTROL(tco->tcobase));
10415e28bf1SPriyanka Gupta 	val |= SP5100_WDT_START_STOP_BIT;
1057cd9d5ffSGuenter Roeck 	writel(val, SP5100_WDT_CONTROL(tco->tcobase));
1067cd9d5ffSGuenter Roeck 
1077cd9d5ffSGuenter Roeck 	return 0;
10815e28bf1SPriyanka Gupta }
10915e28bf1SPriyanka Gupta 
1107cd9d5ffSGuenter Roeck static int tco_timer_stop(struct watchdog_device *wdd)
11115e28bf1SPriyanka Gupta {
1127cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
11315e28bf1SPriyanka Gupta 	u32 val;
11415e28bf1SPriyanka Gupta 
1157cd9d5ffSGuenter Roeck 	val = readl(SP5100_WDT_CONTROL(tco->tcobase));
11615e28bf1SPriyanka Gupta 	val &= ~SP5100_WDT_START_STOP_BIT;
1177cd9d5ffSGuenter Roeck 	writel(val, SP5100_WDT_CONTROL(tco->tcobase));
1187cd9d5ffSGuenter Roeck 
1197cd9d5ffSGuenter Roeck 	return 0;
12015e28bf1SPriyanka Gupta }
12115e28bf1SPriyanka Gupta 
1227cd9d5ffSGuenter Roeck static int tco_timer_ping(struct watchdog_device *wdd)
12315e28bf1SPriyanka Gupta {
1247cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
12515e28bf1SPriyanka Gupta 	u32 val;
12615e28bf1SPriyanka Gupta 
1277cd9d5ffSGuenter Roeck 	val = readl(SP5100_WDT_CONTROL(tco->tcobase));
12815e28bf1SPriyanka Gupta 	val |= SP5100_WDT_TRIGGER_BIT;
1297cd9d5ffSGuenter Roeck 	writel(val, SP5100_WDT_CONTROL(tco->tcobase));
1307cd9d5ffSGuenter Roeck 
1317cd9d5ffSGuenter Roeck 	return 0;
13215e28bf1SPriyanka Gupta }
13315e28bf1SPriyanka Gupta 
1347cd9d5ffSGuenter Roeck static int tco_timer_set_timeout(struct watchdog_device *wdd,
1357cd9d5ffSGuenter Roeck 				 unsigned int t)
13615e28bf1SPriyanka Gupta {
1377cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
13815e28bf1SPriyanka Gupta 
13915e28bf1SPriyanka Gupta 	/* Write new heartbeat to watchdog */
1407cd9d5ffSGuenter Roeck 	writel(t, SP5100_WDT_COUNT(tco->tcobase));
14115e28bf1SPriyanka Gupta 
1427cd9d5ffSGuenter Roeck 	wdd->timeout = t;
1437cd9d5ffSGuenter Roeck 
14415e28bf1SPriyanka Gupta 	return 0;
14515e28bf1SPriyanka Gupta }
14615e28bf1SPriyanka Gupta 
1472b750cffSGuenter Roeck static u8 sp5100_tco_read_pm_reg8(u8 index)
1482b750cffSGuenter Roeck {
1492b750cffSGuenter Roeck 	outb(index, SP5100_IO_PM_INDEX_REG);
1502b750cffSGuenter Roeck 	return inb(SP5100_IO_PM_DATA_REG);
1512b750cffSGuenter Roeck }
1522b750cffSGuenter Roeck 
1532b750cffSGuenter Roeck static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set)
1542b750cffSGuenter Roeck {
1552b750cffSGuenter Roeck 	u8 val;
1562b750cffSGuenter Roeck 
1572b750cffSGuenter Roeck 	outb(index, SP5100_IO_PM_INDEX_REG);
1582b750cffSGuenter Roeck 	val = inb(SP5100_IO_PM_DATA_REG);
1592b750cffSGuenter Roeck 	val &= reset;
1602b750cffSGuenter Roeck 	val |= set;
1612b750cffSGuenter Roeck 	outb(val, SP5100_IO_PM_DATA_REG);
1622b750cffSGuenter Roeck }
1632b750cffSGuenter Roeck 
164887d2ec5SGuenter Roeck static void tco_timer_enable(struct sp5100_tco *tco)
165740fbddfSTakahisa Tanaka {
166887d2ec5SGuenter Roeck 	u32 val;
167887d2ec5SGuenter Roeck 
168887d2ec5SGuenter Roeck 	switch (tco->tco_reg_layout) {
169887d2ec5SGuenter Roeck 	case sb800:
170740fbddfSTakahisa Tanaka 		/* For SB800 or later */
171740fbddfSTakahisa Tanaka 		/* Set the Watchdog timer resolution to 1 sec */
1722b750cffSGuenter Roeck 		sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,
1732b750cffSGuenter Roeck 					  0xff, SB800_PM_WATCHDOG_SECOND_RES);
174740fbddfSTakahisa Tanaka 
175740fbddfSTakahisa Tanaka 		/* Enable watchdog decode bit and watchdog timer */
1762b750cffSGuenter Roeck 		sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,
1772b750cffSGuenter Roeck 					  ~SB800_PM_WATCHDOG_DISABLE,
1782b750cffSGuenter Roeck 					  SB800_PCI_WATCHDOG_DECODE_EN);
179887d2ec5SGuenter Roeck 		break;
180887d2ec5SGuenter Roeck 	case sp5100:
181740fbddfSTakahisa Tanaka 		/* For SP5100 or SB7x0 */
182740fbddfSTakahisa Tanaka 		/* Enable watchdog decode bit */
183740fbddfSTakahisa Tanaka 		pci_read_config_dword(sp5100_tco_pci,
184740fbddfSTakahisa Tanaka 				      SP5100_PCI_WATCHDOG_MISC_REG,
185740fbddfSTakahisa Tanaka 				      &val);
186740fbddfSTakahisa Tanaka 
187740fbddfSTakahisa Tanaka 		val |= SP5100_PCI_WATCHDOG_DECODE_EN;
188740fbddfSTakahisa Tanaka 
189740fbddfSTakahisa Tanaka 		pci_write_config_dword(sp5100_tco_pci,
190740fbddfSTakahisa Tanaka 				       SP5100_PCI_WATCHDOG_MISC_REG,
191740fbddfSTakahisa Tanaka 				       val);
192740fbddfSTakahisa Tanaka 
193740fbddfSTakahisa Tanaka 		/* Enable Watchdog timer and set the resolution to 1 sec */
1942b750cffSGuenter Roeck 		sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,
1952b750cffSGuenter Roeck 					  ~SP5100_PM_WATCHDOG_DISABLE,
1962b750cffSGuenter Roeck 					  SP5100_PM_WATCHDOG_SECOND_RES);
197887d2ec5SGuenter Roeck 		break;
198887d2ec5SGuenter Roeck 	case efch:
199887d2ec5SGuenter Roeck 		/* Set the Watchdog timer resolution to 1 sec and enable */
200887d2ec5SGuenter Roeck 		sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN3,
201887d2ec5SGuenter Roeck 					  ~EFCH_PM_WATCHDOG_DISABLE,
202887d2ec5SGuenter Roeck 					  EFCH_PM_DECODEEN_SECOND_RES);
203887d2ec5SGuenter Roeck 		break;
204740fbddfSTakahisa Tanaka 	}
205740fbddfSTakahisa Tanaka }
206740fbddfSTakahisa Tanaka 
2077cd9d5ffSGuenter Roeck static u32 sp5100_tco_read_pm_reg32(u8 index)
2082b750cffSGuenter Roeck {
2092b750cffSGuenter Roeck 	u32 val = 0;
2102b750cffSGuenter Roeck 	int i;
2112b750cffSGuenter Roeck 
2122b750cffSGuenter Roeck 	for (i = 3; i >= 0; i--)
2132b750cffSGuenter Roeck 		val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);
2142b750cffSGuenter Roeck 
2152b750cffSGuenter Roeck 	return val;
2162b750cffSGuenter Roeck }
2172b750cffSGuenter Roeck 
2187cd9d5ffSGuenter Roeck static int sp5100_tco_setupdevice(struct device *dev,
2197cd9d5ffSGuenter Roeck 				  struct watchdog_device *wdd)
22015e28bf1SPriyanka Gupta {
2217cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
2227cd9d5ffSGuenter Roeck 	const char *dev_name;
223887d2ec5SGuenter Roeck 	u32 mmio_addr = 0, val;
22423dfe140SGuenter Roeck 	int ret;
22515e28bf1SPriyanka Gupta 
22615e28bf1SPriyanka Gupta 	/* Request the IO ports used by this driver */
22716e7730bSGuenter Roeck 	if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,
228887d2ec5SGuenter Roeck 				  SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) {
229fd8f9093SGuenter Roeck 		dev_err(dev, "I/O address 0x%04x already in use\n",
2302b750cffSGuenter Roeck 			SP5100_IO_PM_INDEX_REG);
23123dfe140SGuenter Roeck 		return -EBUSY;
23215e28bf1SPriyanka Gupta 	}
23315e28bf1SPriyanka Gupta 
234740fbddfSTakahisa Tanaka 	/*
235887d2ec5SGuenter Roeck 	 * Determine type of southbridge chipset.
236740fbddfSTakahisa Tanaka 	 */
237887d2ec5SGuenter Roeck 	switch (tco->tco_reg_layout) {
238887d2ec5SGuenter Roeck 	case sp5100:
239887d2ec5SGuenter Roeck 		dev_name = SP5100_DEVNAME;
240887d2ec5SGuenter Roeck 		mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) &
241887d2ec5SGuenter Roeck 								0xfffffff8;
242887d2ec5SGuenter Roeck 		break;
243887d2ec5SGuenter Roeck 	case sb800:
244887d2ec5SGuenter Roeck 		dev_name = SB800_DEVNAME;
245887d2ec5SGuenter Roeck 		mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) &
246887d2ec5SGuenter Roeck 								0xfffffff8;
247887d2ec5SGuenter Roeck 		break;
248887d2ec5SGuenter Roeck 	case efch:
249887d2ec5SGuenter Roeck 		dev_name = SB800_DEVNAME;
250*09da89abSGuenter Roeck 		/*
251*09da89abSGuenter Roeck 		 * On Family 17h devices, the EFCH_PM_DECODEEN_WDT_TMREN bit of
252*09da89abSGuenter Roeck 		 * EFCH_PM_DECODEEN not only enables the EFCH_PM_WDT_ADDR memory
253*09da89abSGuenter Roeck 		 * region, it also enables the watchdog itself.
254*09da89abSGuenter Roeck 		 */
255*09da89abSGuenter Roeck 		if (boot_cpu_data.x86 == 0x17) {
256*09da89abSGuenter Roeck 			val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);
257*09da89abSGuenter Roeck 			if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) {
258*09da89abSGuenter Roeck 				sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN, 0xff,
259*09da89abSGuenter Roeck 							  EFCH_PM_DECODEEN_WDT_TMREN);
260*09da89abSGuenter Roeck 			}
261*09da89abSGuenter Roeck 		}
262887d2ec5SGuenter Roeck 		val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);
263887d2ec5SGuenter Roeck 		if (val & EFCH_PM_DECODEEN_WDT_TMREN)
264887d2ec5SGuenter Roeck 			mmio_addr = EFCH_PM_WDT_ADDR;
265887d2ec5SGuenter Roeck 		break;
266887d2ec5SGuenter Roeck 	default:
267887d2ec5SGuenter Roeck 		return -ENODEV;
268887d2ec5SGuenter Roeck 	}
269740fbddfSTakahisa Tanaka 
270740fbddfSTakahisa Tanaka 	/* Check MMIO address conflict */
271887d2ec5SGuenter Roeck 	if (!mmio_addr ||
272887d2ec5SGuenter Roeck 	    !devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE,
273e189410cSGuenter Roeck 				     dev_name)) {
274887d2ec5SGuenter Roeck 		if (mmio_addr)
275887d2ec5SGuenter Roeck 			dev_dbg(dev, "MMIO address 0x%08x already in use\n",
276887d2ec5SGuenter Roeck 				mmio_addr);
277887d2ec5SGuenter Roeck 		switch (tco->tco_reg_layout) {
278887d2ec5SGuenter Roeck 		case sp5100:
279740fbddfSTakahisa Tanaka 			/*
280740fbddfSTakahisa Tanaka 			 * Secondly, Find the watchdog timer MMIO address
281740fbddfSTakahisa Tanaka 			 * from SBResource_MMIO register.
282740fbddfSTakahisa Tanaka 			 */
283bdecfcdbSHuang Rui 			/* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
284bdecfcdbSHuang Rui 			pci_read_config_dword(sp5100_tco_pci,
285e189410cSGuenter Roeck 					      SP5100_SB_RESOURCE_MMIO_BASE,
286887d2ec5SGuenter Roeck 					      &mmio_addr);
287887d2ec5SGuenter Roeck 			if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN |
288887d2ec5SGuenter Roeck 					  SB800_ACPI_MMIO_SEL)) !=
289740fbddfSTakahisa Tanaka 						  SB800_ACPI_MMIO_DECODE_EN) {
290e189410cSGuenter Roeck 				ret = -ENODEV;
291e189410cSGuenter Roeck 				goto unreg_region;
292e189410cSGuenter Roeck 			}
293887d2ec5SGuenter Roeck 			mmio_addr &= ~0xFFF;
294887d2ec5SGuenter Roeck 			mmio_addr += SB800_PM_WDT_MMIO_OFFSET;
295887d2ec5SGuenter Roeck 			break;
296887d2ec5SGuenter Roeck 		case sb800:
297887d2ec5SGuenter Roeck 			/* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
298887d2ec5SGuenter Roeck 			mmio_addr =
299887d2ec5SGuenter Roeck 				sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN);
300887d2ec5SGuenter Roeck 			if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN |
301887d2ec5SGuenter Roeck 					  SB800_ACPI_MMIO_SEL)) !=
302887d2ec5SGuenter Roeck 						  SB800_ACPI_MMIO_DECODE_EN) {
303887d2ec5SGuenter Roeck 				ret = -ENODEV;
304887d2ec5SGuenter Roeck 				goto unreg_region;
305887d2ec5SGuenter Roeck 			}
306887d2ec5SGuenter Roeck 			mmio_addr &= ~0xFFF;
307887d2ec5SGuenter Roeck 			mmio_addr += SB800_PM_WDT_MMIO_OFFSET;
308887d2ec5SGuenter Roeck 			break;
309887d2ec5SGuenter Roeck 		case efch:
310887d2ec5SGuenter Roeck 			val = sp5100_tco_read_pm_reg8(EFCH_PM_ISACONTROL);
311887d2ec5SGuenter Roeck 			if (!(val & EFCH_PM_ISACONTROL_MMIOEN)) {
312887d2ec5SGuenter Roeck 				ret = -ENODEV;
313887d2ec5SGuenter Roeck 				goto unreg_region;
314887d2ec5SGuenter Roeck 			}
315887d2ec5SGuenter Roeck 			mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +
316887d2ec5SGuenter Roeck 				    EFCH_PM_ACPI_MMIO_WDT_OFFSET;
317887d2ec5SGuenter Roeck 			break;
318887d2ec5SGuenter Roeck 		}
319887d2ec5SGuenter Roeck 		dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n",
320887d2ec5SGuenter Roeck 			mmio_addr);
321887d2ec5SGuenter Roeck 		if (!devm_request_mem_region(dev, mmio_addr,
322887d2ec5SGuenter Roeck 					     SP5100_WDT_MEM_MAP_SIZE,
323740fbddfSTakahisa Tanaka 					     dev_name)) {
324887d2ec5SGuenter Roeck 			dev_dbg(dev, "MMIO address 0x%08x already in use\n",
325887d2ec5SGuenter Roeck 				mmio_addr);
326e189410cSGuenter Roeck 			ret = -EBUSY;
32790d241edSYinghai Lu 			goto unreg_region;
328e189410cSGuenter Roeck 		}
329e189410cSGuenter Roeck 	}
330740fbddfSTakahisa Tanaka 
331887d2ec5SGuenter Roeck 	tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
3327cd9d5ffSGuenter Roeck 	if (!tco->tcobase) {
333fd8f9093SGuenter Roeck 		dev_err(dev, "failed to get tcobase address\n");
33423dfe140SGuenter Roeck 		ret = -ENOMEM;
3357cd9d5ffSGuenter Roeck 		goto unreg_region;
33615e28bf1SPriyanka Gupta 	}
33715e28bf1SPriyanka Gupta 
338887d2ec5SGuenter Roeck 	dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr);
33915e28bf1SPriyanka Gupta 
340740fbddfSTakahisa Tanaka 	/* Setup the watchdog timer */
341887d2ec5SGuenter Roeck 	tco_timer_enable(tco);
34215e28bf1SPriyanka Gupta 
3437cd9d5ffSGuenter Roeck 	val = readl(SP5100_WDT_CONTROL(tco->tcobase));
344f7781b06SGuenter Roeck 	if (val & SP5100_WDT_DISABLED) {
345f7781b06SGuenter Roeck 		dev_err(dev, "Watchdog hardware is disabled\n");
346f7781b06SGuenter Roeck 		ret = -ENODEV;
347f7781b06SGuenter Roeck 		goto unreg_region;
348f7781b06SGuenter Roeck 	}
349f7781b06SGuenter Roeck 
350740fbddfSTakahisa Tanaka 	/*
351740fbddfSTakahisa Tanaka 	 * Save WatchDogFired status, because WatchDogFired flag is
352740fbddfSTakahisa Tanaka 	 * cleared here.
353740fbddfSTakahisa Tanaka 	 */
3547cd9d5ffSGuenter Roeck 	if (val & SP5100_WDT_FIRED)
3557cd9d5ffSGuenter Roeck 		wdd->bootstatus = WDIOF_CARDRESET;
356f7781b06SGuenter Roeck 	/* Set watchdog action to reset the system */
3575bbecc5dSGuenter Roeck 	val &= ~SP5100_WDT_ACTION_RESET;
3587cd9d5ffSGuenter Roeck 	writel(val, SP5100_WDT_CONTROL(tco->tcobase));
35915e28bf1SPriyanka Gupta 
36015e28bf1SPriyanka Gupta 	/* Set a reasonable heartbeat before we stop the timer */
3617cd9d5ffSGuenter Roeck 	tco_timer_set_timeout(wdd, wdd->timeout);
36215e28bf1SPriyanka Gupta 
36315e28bf1SPriyanka Gupta 	/*
36415e28bf1SPriyanka Gupta 	 * Stop the TCO before we change anything so we don't race with
36515e28bf1SPriyanka Gupta 	 * a zeroed timer.
36615e28bf1SPriyanka Gupta 	 */
3677cd9d5ffSGuenter Roeck 	tco_timer_stop(wdd);
36815e28bf1SPriyanka Gupta 
36916e7730bSGuenter Roeck 	release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
370e189410cSGuenter Roeck 
37123dfe140SGuenter Roeck 	return 0;
37215e28bf1SPriyanka Gupta 
37315e28bf1SPriyanka Gupta unreg_region:
3742b750cffSGuenter Roeck 	release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
37523dfe140SGuenter Roeck 	return ret;
37615e28bf1SPriyanka Gupta }
37715e28bf1SPriyanka Gupta 
3787cd9d5ffSGuenter Roeck static struct watchdog_info sp5100_tco_wdt_info = {
3797cd9d5ffSGuenter Roeck 	.identity = "SP5100 TCO timer",
3807cd9d5ffSGuenter Roeck 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
3817cd9d5ffSGuenter Roeck };
3827cd9d5ffSGuenter Roeck 
3837cd9d5ffSGuenter Roeck static const struct watchdog_ops sp5100_tco_wdt_ops = {
3847cd9d5ffSGuenter Roeck 	.owner = THIS_MODULE,
3857cd9d5ffSGuenter Roeck 	.start = tco_timer_start,
3867cd9d5ffSGuenter Roeck 	.stop = tco_timer_stop,
3877cd9d5ffSGuenter Roeck 	.ping = tco_timer_ping,
3887cd9d5ffSGuenter Roeck 	.set_timeout = tco_timer_set_timeout,
3897cd9d5ffSGuenter Roeck };
3907cd9d5ffSGuenter Roeck 
3915bbecc5dSGuenter Roeck static int sp5100_tco_probe(struct platform_device *pdev)
39215e28bf1SPriyanka Gupta {
393fd8f9093SGuenter Roeck 	struct device *dev = &pdev->dev;
3947cd9d5ffSGuenter Roeck 	struct watchdog_device *wdd;
3957cd9d5ffSGuenter Roeck 	struct sp5100_tco *tco;
39615e28bf1SPriyanka Gupta 	int ret;
39715e28bf1SPriyanka Gupta 
3987cd9d5ffSGuenter Roeck 	tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);
3997cd9d5ffSGuenter Roeck 	if (!tco)
4007cd9d5ffSGuenter Roeck 		return -ENOMEM;
4017cd9d5ffSGuenter Roeck 
402887d2ec5SGuenter Roeck 	tco->tco_reg_layout = tco_reg_layout(sp5100_tco_pci);
403887d2ec5SGuenter Roeck 
4047cd9d5ffSGuenter Roeck 	wdd = &tco->wdd;
4057cd9d5ffSGuenter Roeck 	wdd->parent = dev;
4067cd9d5ffSGuenter Roeck 	wdd->info = &sp5100_tco_wdt_info;
4077cd9d5ffSGuenter Roeck 	wdd->ops = &sp5100_tco_wdt_ops;
4087cd9d5ffSGuenter Roeck 	wdd->timeout = WATCHDOG_HEARTBEAT;
4097cd9d5ffSGuenter Roeck 	wdd->min_timeout = 1;
4107cd9d5ffSGuenter Roeck 	wdd->max_timeout = 0xffff;
4117cd9d5ffSGuenter Roeck 
4122d505e3eSWolfram Sang 	watchdog_init_timeout(wdd, heartbeat, NULL);
4137cd9d5ffSGuenter Roeck 	watchdog_set_nowayout(wdd, nowayout);
4147cd9d5ffSGuenter Roeck 	watchdog_stop_on_reboot(wdd);
4157cd9d5ffSGuenter Roeck 	watchdog_stop_on_unregister(wdd);
4167cd9d5ffSGuenter Roeck 	watchdog_set_drvdata(wdd, tco);
4177cd9d5ffSGuenter Roeck 
4187cd9d5ffSGuenter Roeck 	ret = sp5100_tco_setupdevice(dev, wdd);
41923dfe140SGuenter Roeck 	if (ret)
42023dfe140SGuenter Roeck 		return ret;
42115e28bf1SPriyanka Gupta 
4227cd9d5ffSGuenter Roeck 	ret = devm_watchdog_register_device(dev, wdd);
423d41e3f4eSWolfram Sang 	if (ret)
42415e28bf1SPriyanka Gupta 		return ret;
42515e28bf1SPriyanka Gupta 
4267cd9d5ffSGuenter Roeck 	/* Show module parameters */
4277cd9d5ffSGuenter Roeck 	dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
4287cd9d5ffSGuenter Roeck 		 wdd->timeout, nowayout);
42915e28bf1SPriyanka Gupta 
43015e28bf1SPriyanka Gupta 	return 0;
43115e28bf1SPriyanka Gupta }
43215e28bf1SPriyanka Gupta 
43315e28bf1SPriyanka Gupta static struct platform_driver sp5100_tco_driver = {
4345bbecc5dSGuenter Roeck 	.probe		= sp5100_tco_probe,
43515e28bf1SPriyanka Gupta 	.driver		= {
4367cd9d5ffSGuenter Roeck 		.name	= TCO_DRIVER_NAME,
43715e28bf1SPriyanka Gupta 	},
43815e28bf1SPriyanka Gupta };
43915e28bf1SPriyanka Gupta 
440a3483443SGuenter Roeck /*
441a3483443SGuenter Roeck  * Data for PCI driver interface
442a3483443SGuenter Roeck  *
443a3483443SGuenter Roeck  * This data only exists for exporting the supported
444a3483443SGuenter Roeck  * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
445a3483443SGuenter Roeck  * register a pci_driver, because someone else might
446a3483443SGuenter Roeck  * want to register another driver on the same PCI id.
447a3483443SGuenter Roeck  */
448a3483443SGuenter Roeck static const struct pci_device_id sp5100_tco_pci_tbl[] = {
449a3483443SGuenter Roeck 	{ PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
450a3483443SGuenter Roeck 	  PCI_ANY_ID, },
451a3483443SGuenter Roeck 	{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,
452a3483443SGuenter Roeck 	  PCI_ANY_ID, },
453a3483443SGuenter Roeck 	{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
454a3483443SGuenter Roeck 	  PCI_ANY_ID, },
455a3483443SGuenter Roeck 	{ 0, },			/* End of list */
456a3483443SGuenter Roeck };
457a3483443SGuenter Roeck MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
458a3483443SGuenter Roeck 
4595bbecc5dSGuenter Roeck static int __init sp5100_tco_init(void)
46015e28bf1SPriyanka Gupta {
461a3483443SGuenter Roeck 	struct pci_dev *dev = NULL;
46215e28bf1SPriyanka Gupta 	int err;
46315e28bf1SPriyanka Gupta 
464a3483443SGuenter Roeck 	/* Match the PCI device */
465a3483443SGuenter Roeck 	for_each_pci_dev(dev) {
466a3483443SGuenter Roeck 		if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {
467a3483443SGuenter Roeck 			sp5100_tco_pci = dev;
468a3483443SGuenter Roeck 			break;
469a3483443SGuenter Roeck 		}
470a3483443SGuenter Roeck 	}
471a3483443SGuenter Roeck 
472a3483443SGuenter Roeck 	if (!sp5100_tco_pci)
473a3483443SGuenter Roeck 		return -ENODEV;
474a3483443SGuenter Roeck 
4757cd9d5ffSGuenter Roeck 	pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");
47615e28bf1SPriyanka Gupta 
47715e28bf1SPriyanka Gupta 	err = platform_driver_register(&sp5100_tco_driver);
47815e28bf1SPriyanka Gupta 	if (err)
47915e28bf1SPriyanka Gupta 		return err;
48015e28bf1SPriyanka Gupta 
4817cd9d5ffSGuenter Roeck 	sp5100_tco_platform_device =
4827cd9d5ffSGuenter Roeck 		platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);
48315e28bf1SPriyanka Gupta 	if (IS_ERR(sp5100_tco_platform_device)) {
48415e28bf1SPriyanka Gupta 		err = PTR_ERR(sp5100_tco_platform_device);
48515e28bf1SPriyanka Gupta 		goto unreg_platform_driver;
48615e28bf1SPriyanka Gupta 	}
48715e28bf1SPriyanka Gupta 
48815e28bf1SPriyanka Gupta 	return 0;
48915e28bf1SPriyanka Gupta 
49015e28bf1SPriyanka Gupta unreg_platform_driver:
49115e28bf1SPriyanka Gupta 	platform_driver_unregister(&sp5100_tco_driver);
49215e28bf1SPriyanka Gupta 	return err;
49315e28bf1SPriyanka Gupta }
49415e28bf1SPriyanka Gupta 
4955bbecc5dSGuenter Roeck static void __exit sp5100_tco_exit(void)
49615e28bf1SPriyanka Gupta {
49715e28bf1SPriyanka Gupta 	platform_device_unregister(sp5100_tco_platform_device);
49815e28bf1SPriyanka Gupta 	platform_driver_unregister(&sp5100_tco_driver);
49915e28bf1SPriyanka Gupta }
50015e28bf1SPriyanka Gupta 
5015bbecc5dSGuenter Roeck module_init(sp5100_tco_init);
5025bbecc5dSGuenter Roeck module_exit(sp5100_tco_exit);
50315e28bf1SPriyanka Gupta 
50415e28bf1SPriyanka Gupta MODULE_AUTHOR("Priyanka Gupta");
505740fbddfSTakahisa Tanaka MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
50615e28bf1SPriyanka Gupta MODULE_LICENSE("GPL");
507