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