blob: ff240e5be833a833ae2aac324211d2f1a3be66ab [file] [log] [blame]
Priyanka Gupta15e28bf2010-10-25 17:58:04 -07001/*
2 * sp5100_tco : TCO timer driver for sp5100 chipsets
3 *
4 * (c) Copyright 2009 Google Inc., All Rights Reserved.
5 *
6 * Based on i8xx_tco.c:
7 * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
8 * Reserved.
9 * http://www.kernelconcepts.de
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version
14 * 2 of the License, or (at your option) any later version.
15 *
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +090016 * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
17 * AMD Publication 45482 "AMD SB800-Series Southbridges Register
18 * Reference Guide"
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070019 */
20
21/*
22 * Includes, defines, variables, module parameters, ...
23 */
24
Joe Perches27c766a2012-02-15 15:06:19 -080025#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
26
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070027#include <linux/module.h>
28#include <linux/moduleparam.h>
29#include <linux/types.h>
30#include <linux/miscdevice.h>
31#include <linux/watchdog.h>
32#include <linux/init.h>
33#include <linux/fs.h>
34#include <linux/pci.h>
35#include <linux/ioport.h>
36#include <linux/platform_device.h>
37#include <linux/uaccess.h>
38#include <linux/io.h>
39
40#include "sp5100_tco.h"
41
42/* Module and version information */
Takahisa Tanaka18e43212013-03-03 14:52:07 +090043#define TCO_VERSION "0.05"
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070044#define TCO_MODULE_NAME "SP5100 TCO timer"
45#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070046
47/* internal variables */
Yinghai Lu90d241e2011-03-16 20:01:07 -070048static u32 tcobase_phys;
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +090049static u32 tco_wdt_fired;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070050static void __iomem *tcobase;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070051static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
52static unsigned long timer_alive;
53static char tco_expect_close;
54static struct pci_dev *sp5100_tco_pci;
55
56/* the watchdog platform device */
57static struct platform_device *sp5100_tco_platform_device;
58
59/* module parameters */
60
61#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */
62static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
63module_param(heartbeat, int, 0);
64MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
65 __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
66
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010067static bool nowayout = WATCHDOG_NOWAYOUT;
68module_param(nowayout, bool, 0);
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +090069MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070070 " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
71
72/*
73 * Some TCO specific functions
74 */
Lucas Stach46856fa2016-05-03 19:15:58 +020075
76static bool tco_has_sp5100_reg_layout(struct pci_dev *dev)
77{
78 return dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
79 dev->revision < 0x40;
80}
81
Priyanka Gupta15e28bf2010-10-25 17:58:04 -070082static void tco_timer_start(void)
83{
84 u32 val;
85 unsigned long flags;
86
87 spin_lock_irqsave(&tco_lock, flags);
88 val = readl(SP5100_WDT_CONTROL(tcobase));
89 val |= SP5100_WDT_START_STOP_BIT;
90 writel(val, SP5100_WDT_CONTROL(tcobase));
91 spin_unlock_irqrestore(&tco_lock, flags);
92}
93
94static void tco_timer_stop(void)
95{
96 u32 val;
97 unsigned long flags;
98
99 spin_lock_irqsave(&tco_lock, flags);
100 val = readl(SP5100_WDT_CONTROL(tcobase));
101 val &= ~SP5100_WDT_START_STOP_BIT;
102 writel(val, SP5100_WDT_CONTROL(tcobase));
103 spin_unlock_irqrestore(&tco_lock, flags);
104}
105
106static void tco_timer_keepalive(void)
107{
108 u32 val;
109 unsigned long flags;
110
111 spin_lock_irqsave(&tco_lock, flags);
112 val = readl(SP5100_WDT_CONTROL(tcobase));
113 val |= SP5100_WDT_TRIGGER_BIT;
114 writel(val, SP5100_WDT_CONTROL(tcobase));
115 spin_unlock_irqrestore(&tco_lock, flags);
116}
117
118static int tco_timer_set_heartbeat(int t)
119{
120 unsigned long flags;
121
122 if (t < 0 || t > 0xffff)
123 return -EINVAL;
124
125 /* Write new heartbeat to watchdog */
126 spin_lock_irqsave(&tco_lock, flags);
127 writel(t, SP5100_WDT_COUNT(tcobase));
128 spin_unlock_irqrestore(&tco_lock, flags);
129
130 heartbeat = t;
131 return 0;
132}
133
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800134static u8 sp5100_tco_read_pm_reg8(u8 index)
135{
136 outb(index, SP5100_IO_PM_INDEX_REG);
137 return inb(SP5100_IO_PM_DATA_REG);
138}
139
140static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set)
141{
142 u8 val;
143
144 outb(index, SP5100_IO_PM_INDEX_REG);
145 val = inb(SP5100_IO_PM_DATA_REG);
146 val &= reset;
147 val |= set;
148 outb(val, SP5100_IO_PM_DATA_REG);
149}
150
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900151static void tco_timer_enable(void)
152{
Lucas Stach46856fa2016-05-03 19:15:58 +0200153 if (!tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900154 /* For SB800 or later */
155 /* Set the Watchdog timer resolution to 1 sec */
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800156 sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,
157 0xff, SB800_PM_WATCHDOG_SECOND_RES);
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900158
159 /* Enable watchdog decode bit and watchdog timer */
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800160 sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,
161 ~SB800_PM_WATCHDOG_DISABLE,
162 SB800_PCI_WATCHDOG_DECODE_EN);
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900163 } else {
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800164 u32 val;
165
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900166 /* For SP5100 or SB7x0 */
167 /* Enable watchdog decode bit */
168 pci_read_config_dword(sp5100_tco_pci,
169 SP5100_PCI_WATCHDOG_MISC_REG,
170 &val);
171
172 val |= SP5100_PCI_WATCHDOG_DECODE_EN;
173
174 pci_write_config_dword(sp5100_tco_pci,
175 SP5100_PCI_WATCHDOG_MISC_REG,
176 val);
177
178 /* Enable Watchdog timer and set the resolution to 1 sec */
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800179 sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,
180 ~SP5100_PM_WATCHDOG_DISABLE,
181 SP5100_PM_WATCHDOG_SECOND_RES);
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900182 }
183}
184
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700185/*
186 * /dev/watchdog handling
187 */
188
189static int sp5100_tco_open(struct inode *inode, struct file *file)
190{
191 /* /dev/watchdog can only be opened once */
192 if (test_and_set_bit(0, &timer_alive))
193 return -EBUSY;
194
195 /* Reload and activate timer */
196 tco_timer_start();
197 tco_timer_keepalive();
198 return nonseekable_open(inode, file);
199}
200
201static int sp5100_tco_release(struct inode *inode, struct file *file)
202{
203 /* Shut off the timer. */
204 if (tco_expect_close == 42) {
205 tco_timer_stop();
206 } else {
Joe Perches27c766a2012-02-15 15:06:19 -0800207 pr_crit("Unexpected close, not stopping watchdog!\n");
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700208 tco_timer_keepalive();
209 }
210 clear_bit(0, &timer_alive);
211 tco_expect_close = 0;
212 return 0;
213}
214
215static ssize_t sp5100_tco_write(struct file *file, const char __user *data,
216 size_t len, loff_t *ppos)
217{
218 /* See if we got the magic character 'V' and reload the timer */
219 if (len) {
220 if (!nowayout) {
221 size_t i;
222
223 /* note: just in case someone wrote the magic character
224 * five months ago... */
225 tco_expect_close = 0;
226
227 /* scan to see whether or not we got the magic character
228 */
229 for (i = 0; i != len; i++) {
230 char c;
231 if (get_user(c, data + i))
232 return -EFAULT;
233 if (c == 'V')
234 tco_expect_close = 42;
235 }
236 }
237
238 /* someone wrote to us, we should reload the timer */
239 tco_timer_keepalive();
240 }
241 return len;
242}
243
244static long sp5100_tco_ioctl(struct file *file, unsigned int cmd,
245 unsigned long arg)
246{
247 int new_options, retval = -EINVAL;
248 int new_heartbeat;
249 void __user *argp = (void __user *)arg;
250 int __user *p = argp;
251 static const struct watchdog_info ident = {
252 .options = WDIOF_SETTIMEOUT |
253 WDIOF_KEEPALIVEPING |
254 WDIOF_MAGICCLOSE,
255 .firmware_version = 0,
256 .identity = TCO_MODULE_NAME,
257 };
258
259 switch (cmd) {
260 case WDIOC_GETSUPPORT:
261 return copy_to_user(argp, &ident,
262 sizeof(ident)) ? -EFAULT : 0;
263 case WDIOC_GETSTATUS:
264 case WDIOC_GETBOOTSTATUS:
265 return put_user(0, p);
266 case WDIOC_SETOPTIONS:
267 if (get_user(new_options, p))
268 return -EFAULT;
269 if (new_options & WDIOS_DISABLECARD) {
270 tco_timer_stop();
271 retval = 0;
272 }
273 if (new_options & WDIOS_ENABLECARD) {
274 tco_timer_start();
275 tco_timer_keepalive();
276 retval = 0;
277 }
278 return retval;
279 case WDIOC_KEEPALIVE:
280 tco_timer_keepalive();
281 return 0;
282 case WDIOC_SETTIMEOUT:
283 if (get_user(new_heartbeat, p))
284 return -EFAULT;
285 if (tco_timer_set_heartbeat(new_heartbeat))
286 return -EINVAL;
287 tco_timer_keepalive();
288 /* Fall through */
289 case WDIOC_GETTIMEOUT:
290 return put_user(heartbeat, p);
291 default:
292 return -ENOTTY;
293 }
294}
295
296/*
297 * Kernel Interfaces
298 */
299
300static const struct file_operations sp5100_tco_fops = {
301 .owner = THIS_MODULE,
302 .llseek = no_llseek,
303 .write = sp5100_tco_write,
304 .unlocked_ioctl = sp5100_tco_ioctl,
305 .open = sp5100_tco_open,
306 .release = sp5100_tco_release,
307};
308
309static struct miscdevice sp5100_tco_miscdev = {
310 .minor = WATCHDOG_MINOR,
311 .name = "watchdog",
312 .fops = &sp5100_tco_fops,
313};
314
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800315static u8 sp5100_tco_read_pm_reg32(u8 index)
316{
317 u32 val = 0;
318 int i;
319
320 for (i = 3; i >= 0; i--)
321 val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);
322
323 return val;
324}
325
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700326/*
327 * Init & exit routines
328 */
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800329static int sp5100_tco_setupdevice(struct device *dev)
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700330{
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900331 const char *dev_name = NULL;
Takahisa Tanaka18e43212013-03-03 14:52:07 +0900332 u32 val;
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800333 u8 base_addr;
Guenter Roeck23dfe142017-12-24 13:04:09 -0800334 int ret;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700335
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900336 /*
337 * Determine type of southbridge chipset.
338 */
Lucas Stach46856fa2016-05-03 19:15:58 +0200339 if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900340 dev_name = SP5100_DEVNAME;
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900341 base_addr = SP5100_PM_WATCHDOG_BASE;
Huang Ruibdecfcd2015-11-23 18:07:35 +0800342 } else {
343 dev_name = SB800_DEVNAME;
Huang Ruibdecfcd2015-11-23 18:07:35 +0800344 base_addr = SB800_PM_WATCHDOG_BASE;
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900345 }
346
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700347 /* Request the IO ports used by this driver */
Guenter Roeck16e77302017-12-24 13:04:08 -0800348 if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,
349 SP5100_PM_IOPORTS_SIZE, dev_name)) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800350 dev_err(dev, "I/O address 0x%04x already in use\n",
351 SP5100_IO_PM_INDEX_REG);
Guenter Roeck23dfe142017-12-24 13:04:09 -0800352 return -EBUSY;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700353 }
354
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900355 /*
356 * First, Find the watchdog timer MMIO address from indirect I/O.
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800357 * Low three bits of BASE are reserved.
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900358 */
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800359 val = sp5100_tco_read_pm_reg32(base_addr) & 0xfffffff8;
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900360
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800361 dev_dbg(dev, "Got 0x%04x from indirect I/O\n", val);
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900362
363 /* Check MMIO address conflict */
Guenter Roecke1894102017-12-24 13:04:10 -0800364 if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
365 dev_name)) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800366 dev_dbg(dev, "MMIO address 0x%04x already in use\n", val);
Guenter Roecke1894102017-12-24 13:04:10 -0800367 /*
368 * Secondly, Find the watchdog timer MMIO address
369 * from SBResource_MMIO register.
370 */
371 if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
372 /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
373 pci_read_config_dword(sp5100_tco_pci,
374 SP5100_SB_RESOURCE_MMIO_BASE,
375 &val);
376 } else {
377 /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
378 val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN);
379 }
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900380
Guenter Roecke1894102017-12-24 13:04:10 -0800381 /* The SBResource_MMIO is enabled and mapped memory space? */
382 if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) !=
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900383 SB800_ACPI_MMIO_DECODE_EN) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800384 dev_notice(dev,
385 "failed to find MMIO address, giving up.\n");
Guenter Roecke1894102017-12-24 13:04:10 -0800386 ret = -ENODEV;
387 goto unreg_region;
388 }
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900389 /* Clear unnecessary the low twelve bits */
390 val &= ~0xFFF;
391 /* Add the Watchdog Timer offset to base address. */
392 val += SB800_PM_WDT_MMIO_OFFSET;
393 /* Check MMIO address conflict */
Guenter Roecke1894102017-12-24 13:04:10 -0800394 if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
395 dev_name)) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800396 dev_dbg(dev, "MMIO address 0x%04x already in use\n",
397 val);
Guenter Roecke1894102017-12-24 13:04:10 -0800398 ret = -EBUSY;
399 goto unreg_region;
400 }
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800401 dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val);
Guenter Roecke1894102017-12-24 13:04:10 -0800402 }
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900403
Yinghai Lu90d241e2011-03-16 20:01:07 -0700404 tcobase_phys = val;
405
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700406 tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE);
H Hartley Sweeten62a9aeb2012-05-02 16:54:43 -0700407 if (!tcobase) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800408 dev_err(dev, "failed to get tcobase address\n");
Guenter Roeck23dfe142017-12-24 13:04:09 -0800409 ret = -ENOMEM;
Yinghai Lu90d241e2011-03-16 20:01:07 -0700410 goto unreg_mem_region;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700411 }
412
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800413 dev_info(dev, "Using 0x%04x for watchdog MMIO address\n", val);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700414
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900415 /* Setup the watchdog timer */
416 tco_timer_enable();
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700417
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900418 /* Check that the watchdog action is set to reset the system */
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700419 val = readl(SP5100_WDT_CONTROL(tcobase));
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900420 /*
421 * Save WatchDogFired status, because WatchDogFired flag is
422 * cleared here.
423 */
424 tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700425 val &= ~SP5100_PM_WATCHDOG_ACTION_RESET;
426 writel(val, SP5100_WDT_CONTROL(tcobase));
427
428 /* Set a reasonable heartbeat before we stop the timer */
429 tco_timer_set_heartbeat(heartbeat);
430
431 /*
432 * Stop the TCO before we change anything so we don't race with
433 * a zeroed timer.
434 */
435 tco_timer_stop();
436
Guenter Roeck16e77302017-12-24 13:04:08 -0800437 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
Guenter Roecke1894102017-12-24 13:04:10 -0800438
Guenter Roeck23dfe142017-12-24 13:04:09 -0800439 return 0;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700440
Yinghai Lu90d241e2011-03-16 20:01:07 -0700441unreg_mem_region:
442 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700443unreg_region:
Guenter Roeck2b750cf2017-12-24 13:04:06 -0800444 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
Guenter Roeck23dfe142017-12-24 13:04:09 -0800445 return ret;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700446}
447
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800448static int sp5100_tco_init(struct platform_device *pdev)
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700449{
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800450 struct device *dev = &pdev->dev;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700451 int ret;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700452
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900453 /*
454 * Check whether or not the hardware watchdog is there. If found, then
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700455 * set it up.
456 */
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800457 ret = sp5100_tco_setupdevice(dev);
Guenter Roeck23dfe142017-12-24 13:04:09 -0800458 if (ret)
459 return ret;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700460
461 /* Check to see if last reboot was due to watchdog timeout */
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800462 dev_info(dev, "Last reboot was %striggered by watchdog.\n",
463 tco_wdt_fired ? "" : "not ");
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700464
465 /*
466 * Check that the heartbeat value is within it's range.
467 * If not, reset to the default.
468 */
469 if (tco_timer_set_heartbeat(heartbeat)) {
470 heartbeat = WATCHDOG_HEARTBEAT;
471 tco_timer_set_heartbeat(heartbeat);
472 }
473
474 ret = misc_register(&sp5100_tco_miscdev);
475 if (ret != 0) {
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800476 dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n",
477 WATCHDOG_MINOR, ret);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700478 goto exit;
479 }
480
481 clear_bit(0, &timer_alive);
482
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900483 /* Show module parameters */
Guenter Roeckfd8f9092017-12-24 13:04:12 -0800484 dev_info(dev, "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
485 tcobase, heartbeat, nowayout);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700486
487 return 0;
488
489exit:
490 iounmap(tcobase);
Yinghai Lu90d241e2011-03-16 20:01:07 -0700491 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700492 return ret;
493}
494
Bill Pemberton4b12b892012-11-19 13:26:24 -0500495static void sp5100_tco_cleanup(void)
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700496{
497 /* Stop the timer before we leave */
498 if (!nowayout)
499 tco_timer_stop();
500
501 /* Deregister */
502 misc_deregister(&sp5100_tco_miscdev);
503 iounmap(tcobase);
Yinghai Lu90d241e2011-03-16 20:01:07 -0700504 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700505}
506
Bill Pemberton4b12b892012-11-19 13:26:24 -0500507static int sp5100_tco_remove(struct platform_device *dev)
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700508{
509 if (tcobase)
510 sp5100_tco_cleanup();
511 return 0;
512}
513
514static void sp5100_tco_shutdown(struct platform_device *dev)
515{
516 tco_timer_stop();
517}
518
519static struct platform_driver sp5100_tco_driver = {
520 .probe = sp5100_tco_init,
Bill Pemberton82268712012-11-19 13:21:12 -0500521 .remove = sp5100_tco_remove,
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700522 .shutdown = sp5100_tco_shutdown,
523 .driver = {
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700524 .name = TCO_MODULE_NAME,
525 },
526};
527
Guenter Roecka34834432017-12-24 13:04:11 -0800528/*
529 * Data for PCI driver interface
530 *
531 * This data only exists for exporting the supported
532 * PCI ids via MODULE_DEVICE_TABLE. We do not actually
533 * register a pci_driver, because someone else might
534 * want to register another driver on the same PCI id.
535 */
536static const struct pci_device_id sp5100_tco_pci_tbl[] = {
537 { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
538 PCI_ANY_ID, },
539 { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,
540 PCI_ANY_ID, },
541 { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
542 PCI_ANY_ID, },
543 { 0, }, /* End of list */
544};
545MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
546
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700547static int __init sp5100_tco_init_module(void)
548{
Guenter Roecka34834432017-12-24 13:04:11 -0800549 struct pci_dev *dev = NULL;
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700550 int err;
551
Guenter Roecka34834432017-12-24 13:04:11 -0800552 /* Match the PCI device */
553 for_each_pci_dev(dev) {
554 if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {
555 sp5100_tco_pci = dev;
556 break;
557 }
558 }
559
560 if (!sp5100_tco_pci)
561 return -ENODEV;
562
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900563 pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700564
565 err = platform_driver_register(&sp5100_tco_driver);
566 if (err)
567 return err;
568
569 sp5100_tco_platform_device = platform_device_register_simple(
570 TCO_MODULE_NAME, -1, NULL, 0);
571 if (IS_ERR(sp5100_tco_platform_device)) {
572 err = PTR_ERR(sp5100_tco_platform_device);
573 goto unreg_platform_driver;
574 }
575
576 return 0;
577
578unreg_platform_driver:
579 platform_driver_unregister(&sp5100_tco_driver);
580 return err;
581}
582
583static void __exit sp5100_tco_cleanup_module(void)
584{
585 platform_device_unregister(sp5100_tco_platform_device);
586 platform_driver_unregister(&sp5100_tco_driver);
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700587}
588
589module_init(sp5100_tco_init_module);
590module_exit(sp5100_tco_cleanup_module);
591
592MODULE_AUTHOR("Priyanka Gupta");
Takahisa Tanaka740fbdd2012-12-02 14:33:18 +0900593MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
Priyanka Gupta15e28bf2010-10-25 17:58:04 -0700594MODULE_LICENSE("GPL");