1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2016 National Instruments Corp. 4 */ 5 6 #include <linux/bitops.h> 7 #include <linux/device.h> 8 #include <linux/io.h> 9 #include <linux/mod_devicetable.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <linux/types.h> 13 #include <linux/watchdog.h> 14 15 #define LOCK 0xA5 16 #define UNLOCK 0x5A 17 18 #define WDT_CTRL_RESET_EN BIT(7) 19 #define WDT_RELOAD_PORT_EN BIT(7) 20 21 #define WDT_CTRL 1 22 #define WDT_RELOAD_CTRL 2 23 #define WDT_PRESET_PRESCALE 4 24 #define WDT_REG_LOCK 5 25 #define WDT_COUNT 6 26 #define WDT_RELOAD_PORT 7 27 28 #define WDT_MIN_TIMEOUT 1 29 #define WDT_MAX_TIMEOUT 464 30 #define WDT_DEFAULT_TIMEOUT 80 31 32 #define WDT_MAX_COUNTER 15 33 34 static unsigned int timeout; 35 module_param(timeout, uint, 0); 36 MODULE_PARM_DESC(timeout, 37 "Watchdog timeout in seconds. (default=" 38 __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")"); 39 40 static bool nowayout = WATCHDOG_NOWAYOUT; 41 module_param(nowayout, bool, 0); 42 MODULE_PARM_DESC(nowayout, 43 "Watchdog cannot be stopped once started. (default=" 44 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 45 46 struct nic7018_wdt { 47 u16 io_base; 48 u32 period; 49 struct watchdog_device wdd; 50 }; 51 52 struct nic7018_config { 53 u32 period; 54 u8 divider; 55 }; 56 57 static const struct nic7018_config nic7018_configs[] = { 58 { 2, 4 }, 59 { 32, 5 }, 60 }; 61 62 static inline u32 nic7018_timeout(u32 period, u8 counter) 63 { 64 return period * counter - period / 2; 65 } 66 67 static const struct nic7018_config *nic7018_get_config(u32 timeout, 68 u8 *counter) 69 { 70 const struct nic7018_config *config; 71 u8 count; 72 73 if (timeout < 30 && timeout != 16) { 74 config = &nic7018_configs[0]; 75 count = timeout / 2 + 1; 76 } else { 77 config = &nic7018_configs[1]; 78 count = DIV_ROUND_UP(timeout + 16, 32); 79 80 if (count > WDT_MAX_COUNTER) 81 count = WDT_MAX_COUNTER; 82 } 83 *counter = count; 84 return config; 85 } 86 87 static int nic7018_set_timeout(struct watchdog_device *wdd, 88 unsigned int timeout) 89 { 90 struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 91 const struct nic7018_config *config; 92 u8 counter; 93 94 config = nic7018_get_config(timeout, &counter); 95 96 outb(counter << 4 | config->divider, 97 wdt->io_base + WDT_PRESET_PRESCALE); 98 99 wdd->timeout = nic7018_timeout(config->period, counter); 100 wdt->period = config->period; 101 102 return 0; 103 } 104 105 static int nic7018_start(struct watchdog_device *wdd) 106 { 107 struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 108 u8 control; 109 110 nic7018_set_timeout(wdd, wdd->timeout); 111 112 control = inb(wdt->io_base + WDT_RELOAD_CTRL); 113 outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL); 114 115 outb(1, wdt->io_base + WDT_RELOAD_PORT); 116 117 control = inb(wdt->io_base + WDT_CTRL); 118 outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL); 119 120 return 0; 121 } 122 123 static int nic7018_stop(struct watchdog_device *wdd) 124 { 125 struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 126 127 outb(0, wdt->io_base + WDT_CTRL); 128 outb(0, wdt->io_base + WDT_RELOAD_CTRL); 129 outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE); 130 131 return 0; 132 } 133 134 static int nic7018_ping(struct watchdog_device *wdd) 135 { 136 struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 137 138 outb(1, wdt->io_base + WDT_RELOAD_PORT); 139 140 return 0; 141 } 142 143 static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd) 144 { 145 struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 146 u8 count; 147 148 count = inb(wdt->io_base + WDT_COUNT) & 0xF; 149 if (!count) 150 return 0; 151 152 return nic7018_timeout(wdt->period, count); 153 } 154 155 static const struct watchdog_info nic7018_wdd_info = { 156 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 157 .identity = "NIC7018 Watchdog", 158 }; 159 160 static const struct watchdog_ops nic7018_wdd_ops = { 161 .owner = THIS_MODULE, 162 .start = nic7018_start, 163 .stop = nic7018_stop, 164 .ping = nic7018_ping, 165 .set_timeout = nic7018_set_timeout, 166 .get_timeleft = nic7018_get_timeleft, 167 }; 168 169 static int nic7018_probe(struct platform_device *pdev) 170 { 171 struct device *dev = &pdev->dev; 172 struct watchdog_device *wdd; 173 struct nic7018_wdt *wdt; 174 struct resource *io_rc; 175 int ret; 176 177 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 178 if (!wdt) 179 return -ENOMEM; 180 181 platform_set_drvdata(pdev, wdt); 182 183 io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0); 184 if (!io_rc) { 185 dev_err(dev, "missing IO resources\n"); 186 return -EINVAL; 187 } 188 189 if (!devm_request_region(dev, io_rc->start, resource_size(io_rc), 190 KBUILD_MODNAME)) { 191 dev_err(dev, "failed to get IO region\n"); 192 return -EBUSY; 193 } 194 195 wdt->io_base = io_rc->start; 196 wdd = &wdt->wdd; 197 wdd->info = &nic7018_wdd_info; 198 wdd->ops = &nic7018_wdd_ops; 199 wdd->min_timeout = WDT_MIN_TIMEOUT; 200 wdd->max_timeout = WDT_MAX_TIMEOUT; 201 wdd->timeout = WDT_DEFAULT_TIMEOUT; 202 wdd->parent = dev; 203 204 watchdog_set_drvdata(wdd, wdt); 205 watchdog_set_nowayout(wdd, nowayout); 206 watchdog_init_timeout(wdd, timeout, dev); 207 208 /* Unlock WDT register */ 209 outb(UNLOCK, wdt->io_base + WDT_REG_LOCK); 210 211 ret = watchdog_register_device(wdd); 212 if (ret) { 213 outb(LOCK, wdt->io_base + WDT_REG_LOCK); 214 return ret; 215 } 216 217 dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n", 218 wdt->io_base, timeout, nowayout); 219 return 0; 220 } 221 222 static void nic7018_remove(struct platform_device *pdev) 223 { 224 struct nic7018_wdt *wdt = platform_get_drvdata(pdev); 225 226 watchdog_unregister_device(&wdt->wdd); 227 228 /* Lock WDT register */ 229 outb(LOCK, wdt->io_base + WDT_REG_LOCK); 230 } 231 232 static const struct acpi_device_id nic7018_device_ids[] = { 233 { "NIC7018" }, 234 { } 235 }; 236 MODULE_DEVICE_TABLE(acpi, nic7018_device_ids); 237 238 static struct platform_driver watchdog_driver = { 239 .probe = nic7018_probe, 240 .remove = nic7018_remove, 241 .driver = { 242 .name = KBUILD_MODNAME, 243 .acpi_match_table = nic7018_device_ids, 244 }, 245 }; 246 247 module_platform_driver(watchdog_driver); 248 249 MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver"); 250 MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>"); 251 MODULE_LICENSE("GPL"); 252