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