1*dc3c56b7SMarc Vertes /* 2*dc3c56b7SMarc Vertes * VIA Chipset Watchdog Driver 3*dc3c56b7SMarc Vertes * 4*dc3c56b7SMarc Vertes * Copyright (C) 2011 Sigfox 5*dc3c56b7SMarc Vertes * License terms: GNU General Public License (GPL) version 2 6*dc3c56b7SMarc Vertes * Author: Marc Vertes <marc.vertes@sigfox.com> 7*dc3c56b7SMarc Vertes * Based on a preliminary version from Harald Welte <HaraldWelte@viatech.com> 8*dc3c56b7SMarc Vertes * Timer code by Wim Van Sebroeck <wim@iguana.be> 9*dc3c56b7SMarc Vertes * 10*dc3c56b7SMarc Vertes * Caveat: PnP must be enabled in BIOS to allow full access to watchdog 11*dc3c56b7SMarc Vertes * control registers. If not, the watchdog must be configured in BIOS manually. 12*dc3c56b7SMarc Vertes */ 13*dc3c56b7SMarc Vertes #include <linux/device.h> 14*dc3c56b7SMarc Vertes #include <linux/io.h> 15*dc3c56b7SMarc Vertes #include <linux/jiffies.h> 16*dc3c56b7SMarc Vertes #include <linux/module.h> 17*dc3c56b7SMarc Vertes #include <linux/pci.h> 18*dc3c56b7SMarc Vertes #include <linux/timer.h> 19*dc3c56b7SMarc Vertes #include <linux/watchdog.h> 20*dc3c56b7SMarc Vertes 21*dc3c56b7SMarc Vertes /* Configuration registers relative to the pci device */ 22*dc3c56b7SMarc Vertes #define VIA_WDT_MMIO_BASE 0xe8 /* MMIO region base address */ 23*dc3c56b7SMarc Vertes #define VIA_WDT_CONF 0xec /* watchdog enable state */ 24*dc3c56b7SMarc Vertes 25*dc3c56b7SMarc Vertes /* Relevant bits for the VIA_WDT_CONF register */ 26*dc3c56b7SMarc Vertes #define VIA_WDT_CONF_ENABLE 0x01 /* 1: enable watchdog */ 27*dc3c56b7SMarc Vertes #define VIA_WDT_CONF_MMIO 0x02 /* 1: enable watchdog MMIO */ 28*dc3c56b7SMarc Vertes 29*dc3c56b7SMarc Vertes /* 30*dc3c56b7SMarc Vertes * The MMIO region contains the watchog control register and the 31*dc3c56b7SMarc Vertes * hardware timer counter. 32*dc3c56b7SMarc Vertes */ 33*dc3c56b7SMarc Vertes #define VIA_WDT_MMIO_LEN 8 /* MMIO region length in bytes */ 34*dc3c56b7SMarc Vertes #define VIA_WDT_CTL 0 /* MMIO addr+0: state/control reg. */ 35*dc3c56b7SMarc Vertes #define VIA_WDT_COUNT 4 /* MMIO addr+4: timer counter reg. */ 36*dc3c56b7SMarc Vertes 37*dc3c56b7SMarc Vertes /* Bits for the VIA_WDT_CTL register */ 38*dc3c56b7SMarc Vertes #define VIA_WDT_RUNNING 0x01 /* 0: stop, 1: running */ 39*dc3c56b7SMarc Vertes #define VIA_WDT_FIRED 0x02 /* 1: restarted by expired watchdog */ 40*dc3c56b7SMarc Vertes #define VIA_WDT_PWROFF 0x04 /* 0: reset, 1: poweroff */ 41*dc3c56b7SMarc Vertes #define VIA_WDT_DISABLED 0x08 /* 1: timer is disabled */ 42*dc3c56b7SMarc Vertes #define VIA_WDT_TRIGGER 0x80 /* 1: start a new countdown */ 43*dc3c56b7SMarc Vertes 44*dc3c56b7SMarc Vertes /* Hardware heartbeat in seconds */ 45*dc3c56b7SMarc Vertes #define WDT_HW_HEARTBEAT 1 46*dc3c56b7SMarc Vertes 47*dc3c56b7SMarc Vertes /* Timer heartbeat (500ms) */ 48*dc3c56b7SMarc Vertes #define WDT_HEARTBEAT (HZ/2) /* should be <= ((WDT_HW_HEARTBEAT*HZ)/2) */ 49*dc3c56b7SMarc Vertes 50*dc3c56b7SMarc Vertes /* User space timeout in seconds */ 51*dc3c56b7SMarc Vertes #define WDT_TIMEOUT_MAX 1023 /* approx. 17 min. */ 52*dc3c56b7SMarc Vertes #define WDT_TIMEOUT 60 53*dc3c56b7SMarc Vertes static int timeout = WDT_TIMEOUT; 54*dc3c56b7SMarc Vertes module_param(timeout, int, 0); 55*dc3c56b7SMarc Vertes MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, between 1 and 1023 " 56*dc3c56b7SMarc Vertes "(default = " __MODULE_STRING(WDT_TIMEOUT) ")"); 57*dc3c56b7SMarc Vertes 58*dc3c56b7SMarc Vertes static int nowayout = WATCHDOG_NOWAYOUT; 59*dc3c56b7SMarc Vertes module_param(nowayout, int, 0); 60*dc3c56b7SMarc Vertes MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 61*dc3c56b7SMarc Vertes "(default = " __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 62*dc3c56b7SMarc Vertes 63*dc3c56b7SMarc Vertes static struct watchdog_device wdt_dev; 64*dc3c56b7SMarc Vertes static struct resource wdt_res; 65*dc3c56b7SMarc Vertes static void __iomem *wdt_mem; 66*dc3c56b7SMarc Vertes static unsigned int mmio; 67*dc3c56b7SMarc Vertes static void wdt_timer_tick(unsigned long data); 68*dc3c56b7SMarc Vertes static DEFINE_TIMER(timer, wdt_timer_tick, 0, 0); 69*dc3c56b7SMarc Vertes /* The timer that pings the watchdog */ 70*dc3c56b7SMarc Vertes static unsigned long next_heartbeat; /* the next_heartbeat for the timer */ 71*dc3c56b7SMarc Vertes 72*dc3c56b7SMarc Vertes static inline void wdt_reset(void) 73*dc3c56b7SMarc Vertes { 74*dc3c56b7SMarc Vertes unsigned int ctl = readl(wdt_mem); 75*dc3c56b7SMarc Vertes 76*dc3c56b7SMarc Vertes writel(ctl | VIA_WDT_TRIGGER, wdt_mem); 77*dc3c56b7SMarc Vertes } 78*dc3c56b7SMarc Vertes 79*dc3c56b7SMarc Vertes /* 80*dc3c56b7SMarc Vertes * Timer tick: the timer will make sure that the watchdog timer hardware 81*dc3c56b7SMarc Vertes * is being reset in time. The conditions to do this are: 82*dc3c56b7SMarc Vertes * 1) the watchog timer has been started and /dev/watchdog is open 83*dc3c56b7SMarc Vertes * and there is still time left before userspace should send the 84*dc3c56b7SMarc Vertes * next heartbeat/ping. (note: the internal heartbeat is much smaller 85*dc3c56b7SMarc Vertes * then the external/userspace heartbeat). 86*dc3c56b7SMarc Vertes * 2) the watchdog timer has been stopped by userspace. 87*dc3c56b7SMarc Vertes */ 88*dc3c56b7SMarc Vertes static void wdt_timer_tick(unsigned long data) 89*dc3c56b7SMarc Vertes { 90*dc3c56b7SMarc Vertes if (time_before(jiffies, next_heartbeat) || 91*dc3c56b7SMarc Vertes (!test_bit(WDOG_ACTIVE, &wdt_dev.status))) { 92*dc3c56b7SMarc Vertes wdt_reset(); 93*dc3c56b7SMarc Vertes mod_timer(&timer, jiffies + WDT_HEARTBEAT); 94*dc3c56b7SMarc Vertes } else 95*dc3c56b7SMarc Vertes pr_crit("I will reboot your machine !\n"); 96*dc3c56b7SMarc Vertes } 97*dc3c56b7SMarc Vertes 98*dc3c56b7SMarc Vertes static int wdt_ping(struct watchdog_device *wdd) 99*dc3c56b7SMarc Vertes { 100*dc3c56b7SMarc Vertes /* calculate when the next userspace timeout will be */ 101*dc3c56b7SMarc Vertes next_heartbeat = jiffies + timeout * HZ; 102*dc3c56b7SMarc Vertes return 0; 103*dc3c56b7SMarc Vertes } 104*dc3c56b7SMarc Vertes 105*dc3c56b7SMarc Vertes static int wdt_start(struct watchdog_device *wdd) 106*dc3c56b7SMarc Vertes { 107*dc3c56b7SMarc Vertes unsigned int ctl = readl(wdt_mem); 108*dc3c56b7SMarc Vertes 109*dc3c56b7SMarc Vertes writel(timeout, wdt_mem + VIA_WDT_COUNT); 110*dc3c56b7SMarc Vertes writel(ctl | VIA_WDT_RUNNING | VIA_WDT_TRIGGER, wdt_mem); 111*dc3c56b7SMarc Vertes wdt_ping(wdd); 112*dc3c56b7SMarc Vertes mod_timer(&timer, jiffies + WDT_HEARTBEAT); 113*dc3c56b7SMarc Vertes return 0; 114*dc3c56b7SMarc Vertes } 115*dc3c56b7SMarc Vertes 116*dc3c56b7SMarc Vertes static int wdt_stop(struct watchdog_device *wdd) 117*dc3c56b7SMarc Vertes { 118*dc3c56b7SMarc Vertes unsigned int ctl = readl(wdt_mem); 119*dc3c56b7SMarc Vertes 120*dc3c56b7SMarc Vertes writel(ctl & ~VIA_WDT_RUNNING, wdt_mem); 121*dc3c56b7SMarc Vertes return 0; 122*dc3c56b7SMarc Vertes } 123*dc3c56b7SMarc Vertes 124*dc3c56b7SMarc Vertes static int wdt_set_timeout(struct watchdog_device *wdd, 125*dc3c56b7SMarc Vertes unsigned int new_timeout) 126*dc3c56b7SMarc Vertes { 127*dc3c56b7SMarc Vertes if (new_timeout < 1 || new_timeout > WDT_TIMEOUT_MAX) 128*dc3c56b7SMarc Vertes return -EINVAL; 129*dc3c56b7SMarc Vertes writel(new_timeout, wdt_mem + VIA_WDT_COUNT); 130*dc3c56b7SMarc Vertes timeout = new_timeout; 131*dc3c56b7SMarc Vertes return 0; 132*dc3c56b7SMarc Vertes } 133*dc3c56b7SMarc Vertes 134*dc3c56b7SMarc Vertes static const struct watchdog_info wdt_info = { 135*dc3c56b7SMarc Vertes .identity = "VIA watchdog", 136*dc3c56b7SMarc Vertes .options = WDIOF_CARDRESET | 137*dc3c56b7SMarc Vertes WDIOF_SETTIMEOUT | 138*dc3c56b7SMarc Vertes WDIOF_MAGICCLOSE | 139*dc3c56b7SMarc Vertes WDIOF_KEEPALIVEPING, 140*dc3c56b7SMarc Vertes }; 141*dc3c56b7SMarc Vertes 142*dc3c56b7SMarc Vertes static const struct watchdog_ops wdt_ops = { 143*dc3c56b7SMarc Vertes .owner = THIS_MODULE, 144*dc3c56b7SMarc Vertes .start = wdt_start, 145*dc3c56b7SMarc Vertes .stop = wdt_stop, 146*dc3c56b7SMarc Vertes .ping = wdt_ping, 147*dc3c56b7SMarc Vertes .set_timeout = wdt_set_timeout, 148*dc3c56b7SMarc Vertes }; 149*dc3c56b7SMarc Vertes 150*dc3c56b7SMarc Vertes static struct watchdog_device wdt_dev = { 151*dc3c56b7SMarc Vertes .info = &wdt_info, 152*dc3c56b7SMarc Vertes .ops = &wdt_ops, 153*dc3c56b7SMarc Vertes }; 154*dc3c56b7SMarc Vertes 155*dc3c56b7SMarc Vertes static int __devinit wdt_probe(struct pci_dev *pdev, 156*dc3c56b7SMarc Vertes const struct pci_device_id *ent) 157*dc3c56b7SMarc Vertes { 158*dc3c56b7SMarc Vertes unsigned char conf; 159*dc3c56b7SMarc Vertes int ret = -ENODEV; 160*dc3c56b7SMarc Vertes 161*dc3c56b7SMarc Vertes if (pci_enable_device(pdev)) { 162*dc3c56b7SMarc Vertes dev_err(&pdev->dev, "cannot enable PCI device\n"); 163*dc3c56b7SMarc Vertes return -ENODEV; 164*dc3c56b7SMarc Vertes } 165*dc3c56b7SMarc Vertes 166*dc3c56b7SMarc Vertes /* 167*dc3c56b7SMarc Vertes * Allocate a MMIO region which contains watchdog control register 168*dc3c56b7SMarc Vertes * and counter, then configure the watchdog to use this region. 169*dc3c56b7SMarc Vertes * This is possible only if PnP is properly enabled in BIOS. 170*dc3c56b7SMarc Vertes * If not, the watchdog must be configured in BIOS manually. 171*dc3c56b7SMarc Vertes */ 172*dc3c56b7SMarc Vertes if (allocate_resource(&iomem_resource, &wdt_res, VIA_WDT_MMIO_LEN, 173*dc3c56b7SMarc Vertes 0xf0000000, 0xffffff00, 0xff, NULL, NULL)) { 174*dc3c56b7SMarc Vertes dev_err(&pdev->dev, "MMIO allocation failed\n"); 175*dc3c56b7SMarc Vertes goto err_out_disable_device; 176*dc3c56b7SMarc Vertes } 177*dc3c56b7SMarc Vertes 178*dc3c56b7SMarc Vertes pci_write_config_dword(pdev, VIA_WDT_MMIO_BASE, wdt_res.start); 179*dc3c56b7SMarc Vertes pci_read_config_byte(pdev, VIA_WDT_CONF, &conf); 180*dc3c56b7SMarc Vertes conf |= VIA_WDT_CONF_ENABLE | VIA_WDT_CONF_MMIO; 181*dc3c56b7SMarc Vertes pci_write_config_byte(pdev, VIA_WDT_CONF, conf); 182*dc3c56b7SMarc Vertes 183*dc3c56b7SMarc Vertes pci_read_config_dword(pdev, VIA_WDT_MMIO_BASE, &mmio); 184*dc3c56b7SMarc Vertes if (mmio) { 185*dc3c56b7SMarc Vertes dev_info(&pdev->dev, "VIA Chipset watchdog MMIO: %x\n", mmio); 186*dc3c56b7SMarc Vertes } else { 187*dc3c56b7SMarc Vertes dev_err(&pdev->dev, "MMIO setting failed. Check BIOS.\n"); 188*dc3c56b7SMarc Vertes goto err_out_resource; 189*dc3c56b7SMarc Vertes } 190*dc3c56b7SMarc Vertes 191*dc3c56b7SMarc Vertes if (!request_mem_region(mmio, VIA_WDT_MMIO_LEN, "via_wdt")) { 192*dc3c56b7SMarc Vertes dev_err(&pdev->dev, "MMIO region busy\n"); 193*dc3c56b7SMarc Vertes goto err_out_resource; 194*dc3c56b7SMarc Vertes } 195*dc3c56b7SMarc Vertes 196*dc3c56b7SMarc Vertes wdt_mem = ioremap(mmio, VIA_WDT_MMIO_LEN); 197*dc3c56b7SMarc Vertes if (wdt_mem == NULL) { 198*dc3c56b7SMarc Vertes dev_err(&pdev->dev, "cannot remap VIA wdt MMIO registers\n"); 199*dc3c56b7SMarc Vertes goto err_out_release; 200*dc3c56b7SMarc Vertes } 201*dc3c56b7SMarc Vertes 202*dc3c56b7SMarc Vertes wdt_dev.timeout = timeout; 203*dc3c56b7SMarc Vertes watchdog_set_nowayout(&wdt_dev, nowayout); 204*dc3c56b7SMarc Vertes if (readl(wdt_mem) & VIA_WDT_FIRED) 205*dc3c56b7SMarc Vertes wdt_dev.bootstatus |= WDIOF_CARDRESET; 206*dc3c56b7SMarc Vertes 207*dc3c56b7SMarc Vertes ret = watchdog_register_device(&wdt_dev); 208*dc3c56b7SMarc Vertes if (ret) 209*dc3c56b7SMarc Vertes goto err_out_iounmap; 210*dc3c56b7SMarc Vertes 211*dc3c56b7SMarc Vertes /* start triggering, in case of watchdog already enabled by BIOS */ 212*dc3c56b7SMarc Vertes mod_timer(&timer, jiffies + WDT_HEARTBEAT); 213*dc3c56b7SMarc Vertes return 0; 214*dc3c56b7SMarc Vertes 215*dc3c56b7SMarc Vertes err_out_iounmap: 216*dc3c56b7SMarc Vertes iounmap(wdt_mem); 217*dc3c56b7SMarc Vertes err_out_release: 218*dc3c56b7SMarc Vertes release_mem_region(mmio, VIA_WDT_MMIO_LEN); 219*dc3c56b7SMarc Vertes err_out_resource: 220*dc3c56b7SMarc Vertes release_resource(&wdt_res); 221*dc3c56b7SMarc Vertes err_out_disable_device: 222*dc3c56b7SMarc Vertes pci_disable_device(pdev); 223*dc3c56b7SMarc Vertes return ret; 224*dc3c56b7SMarc Vertes } 225*dc3c56b7SMarc Vertes 226*dc3c56b7SMarc Vertes static void __devexit wdt_remove(struct pci_dev *pdev) 227*dc3c56b7SMarc Vertes { 228*dc3c56b7SMarc Vertes watchdog_unregister_device(&wdt_dev); 229*dc3c56b7SMarc Vertes del_timer(&timer); 230*dc3c56b7SMarc Vertes iounmap(wdt_mem); 231*dc3c56b7SMarc Vertes release_mem_region(mmio, VIA_WDT_MMIO_LEN); 232*dc3c56b7SMarc Vertes release_resource(&wdt_res); 233*dc3c56b7SMarc Vertes pci_disable_device(pdev); 234*dc3c56b7SMarc Vertes } 235*dc3c56b7SMarc Vertes 236*dc3c56b7SMarc Vertes DEFINE_PCI_DEVICE_TABLE(wdt_pci_table) = { 237*dc3c56b7SMarc Vertes { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_CX700) }, 238*dc3c56b7SMarc Vertes { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX800) }, 239*dc3c56b7SMarc Vertes { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX855) }, 240*dc3c56b7SMarc Vertes { 0 } 241*dc3c56b7SMarc Vertes }; 242*dc3c56b7SMarc Vertes 243*dc3c56b7SMarc Vertes static struct pci_driver wdt_driver = { 244*dc3c56b7SMarc Vertes .name = "via_wdt", 245*dc3c56b7SMarc Vertes .id_table = wdt_pci_table, 246*dc3c56b7SMarc Vertes .probe = wdt_probe, 247*dc3c56b7SMarc Vertes .remove = __devexit_p(wdt_remove), 248*dc3c56b7SMarc Vertes }; 249*dc3c56b7SMarc Vertes 250*dc3c56b7SMarc Vertes static int __init wdt_init(void) 251*dc3c56b7SMarc Vertes { 252*dc3c56b7SMarc Vertes if (timeout < 1 || timeout > WDT_TIMEOUT_MAX) 253*dc3c56b7SMarc Vertes timeout = WDT_TIMEOUT; 254*dc3c56b7SMarc Vertes return pci_register_driver(&wdt_driver); 255*dc3c56b7SMarc Vertes } 256*dc3c56b7SMarc Vertes 257*dc3c56b7SMarc Vertes static void __exit wdt_exit(void) 258*dc3c56b7SMarc Vertes { 259*dc3c56b7SMarc Vertes pci_unregister_driver(&wdt_driver); 260*dc3c56b7SMarc Vertes } 261*dc3c56b7SMarc Vertes 262*dc3c56b7SMarc Vertes module_init(wdt_init); 263*dc3c56b7SMarc Vertes module_exit(wdt_exit); 264*dc3c56b7SMarc Vertes 265*dc3c56b7SMarc Vertes MODULE_AUTHOR("Marc Vertes"); 266*dc3c56b7SMarc Vertes MODULE_DESCRIPTION("Driver for watchdog timer on VIA chipset"); 267*dc3c56b7SMarc Vertes MODULE_LICENSE("GPL"); 268